Używanie notacji listy Pythona

Biznes

W Pythonie łatwo jest używać notacji list comprehensions podczas generowania nowej listy.(List comprehensions)

W tym artykule omówimy najpierw następujące zagadnienia

  • Podstawowy typ notacji rozumienia listy
  • Notacja rozumienia listy z warunkowym rozgałęzianiem przez if
  • Kombinacja z operatorami trójskładnikowymi (if else-like processing)
  • zip(),enumerate()Kombinacja z tymi
  • notacja włączenia listy zagnieżdżonej

Następnie wyjaśnimy zestaw notacji list comprehension wraz z przykładowym kodem.

  • notacja włączania zbiorów(Set comprehensions)
  • notacja włączenia słownika(Dict comprehensions)
  • typ generatora(Generator expressions)

Podstawowy typ notacji rozumienia listy

Notacja list comprehension jest zapisywana w następujący sposób.

[Expression for Any Variable Name in Iterable Object]

Bierze każdy element obiektu iterowalnego, takiego jak lista, tuple lub zakres przez dowolną nazwę zmiennej i ocenia go za pomocą wyrażenia. Zwracana jest nowa lista z wynikiem obliczenia jako elementem.

Podany jest przykład wraz z równoważną deklaracją for.

squares = [i**2 for i in range(5)]
print(squares)
# [0, 1, 4, 9, 16]
squares = []
for i in range(5):
    squares.append(i**2)

print(squares)
# [0, 1, 4, 9, 16]

Ten sam proces może być wykonany za pomocą map(), ale notacja list comprehension jest preferowana ze względu na jej prostotę i przejrzystość.

Notacja rozumienia listy z warunkowym rozgałęzianiem przez if

Możliwe jest również warunkowe rozgałęzianie za pomocą if. Napisz if w postfiksie w następujący sposób.

[Expression for Any Variable Name in Iterable Object if Conditional Expression]

Tylko te elementy obiektu iterowalnego, których wyrażenie warunkowe jest prawdziwe, są obliczane przez wyrażenie i zwracana jest nowa lista, której elementy są wynikiem.

W wyrażeniu warunkowym można użyć dowolnej nazwy zmiennej.

Podany jest przykład wraz z równoważną deklaracją for.

odds = [i for i in range(10) if i % 2 == 1]
print(odds)
# [1, 3, 5, 7, 9]
odds = []
for i in range(10):
    if i % 2 == 1:
        odds.append(i)

print(odds)
# [1, 3, 5, 7, 9]

Ten sam proces może być wykonany za pomocą filter(), ale notacja list comprehension jest preferowana ze względu na jej prostotę i przejrzystość.

Kombinacja z operatorami trójskładnikowymi (if else-like processing)

W powyższym przykładzie przetwarzane są tylko te elementy, które spełniają kryteria, a te, które ich nie spełniają, są wykluczane z nowej listy.

Jeśli chcesz przełączyć proces w zależności od warunku, lub jeśli chcesz przetworzyć elementy, które nie spełniają warunku inaczej, jak w if else, użyj operatora trójskładnikowego.

W Pythonie operator trójskładnikowy można zapisać następująco

Value When True if Conditional Expression else Value When False

Jest to używane w części wyrażeniowej notacji list comprehension, jak pokazano poniżej.

[Value When True if Conditional Expression else Value When False for Any Variable Name in Iterable Object]

Podany jest przykład wraz z równoważną deklaracją for.

odd_even = ['odd' if i % 2 == 1 else 'even' for i in range(10)]
print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
odd_even = []
for i in range(10):
    if i % 2 == 1:
        odd_even.append('odd')
    else:
        odd_even.append('even')

print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']

Możliwe jest również pisanie wyrażeń z użyciem dowolnych nazw zmiennych dla wartości true i false.

Jeśli warunek jest spełniony, wykonywane jest przetwarzanie, w przeciwnym razie wartość oryginalnego obiektu iterowalnego jest pozostawiana bez zmian.

odd10 = [i * 10 if i % 2 == 1 else i for i in range(10)]
print(odd10)
# [0, 10, 2, 30, 4, 50, 6, 70, 8, 90]

Połączenie z zip() i enumerate()

Użyteczne funkcje, które są często używane w instrukcji for to zip(), która łączy wiele iterabli, oraz enumerate(), która zwraca wartość wraz z jej indeksem.

Oczywiście, możliwe jest użycie zip() i enumerate() z notacją list comprehension. Nie jest to specjalna składnia i nie jest to trudne, jeśli weźmiemy pod uwagę korespondencję z instrukcją for.

Przykład funkcji zip().

l_str1 = ['a', 'b', 'c']
l_str2 = ['x', 'y', 'z']

l_zip = [(s1, s2) for s1, s2 in zip(l_str1, l_str2)]
print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]
l_zip = []
for s1, s2 in zip(l_str1, l_str2):
    l_zip.append((s1, s2))

print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]

Przykład funkcji enumerate().

l_enu = [(i, s) for i, s in enumerate(l_str1)]
print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]
l_enu = []
for i, s in enumerate(l_str1):
    l_enu.append((i, s))

print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]

Idea jest taka sama jak poprzednio, gdy używamy if.

l_zip_if = [(s1, s2) for s1, s2 in zip(l_str1, l_str2) if s1 != 'b']
print(l_zip_if)
# [('a', 'x'), ('c', 'z')]

Każdy element może być również użyty do obliczenia nowego elementu.

l_int1 = [1, 2, 3]
l_int2 = [10, 20, 30]

l_sub = [i2 - i1 for i1, i2 in zip(l_int1, l_int2)]
print(l_sub)
# [9, 18, 27]

notacja włączenia listy zagnieżdżonej

Podobnie jak zagnieżdżanie pętli for, notacja list comprehension również może być zagnieżdżana.

[Expression for Variable Name 1 in Iterable Object 1
    for Variable Name 2 in Iterable Object 2
        for Variable Name 3 in Iterable Object 3 ... ]

Dla wygody dodano podziały wierszy i wcięcia, ale nie są one wymagane ze względów gramatycznych; mogą być kontynuowane w jednej linii.

Podany jest przykład wraz z równoważną deklaracją for.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

flat = [x for row in matrix for x in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat = []
for row in matrix:
    for x in row:
        flat.append(x)

print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Możliwe jest również użycie wielu zmiennych.

cells = [(row, col) for row in range(3) for col in range(2)]
print(cells)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

Możesz również wykonać rozgałęzienie warunkowe.

cells = [(row, col) for row in range(3)
         for col in range(2) if col == row]
print(cells)
# [(0, 0), (1, 1)]

Możliwe jest również warunkowe rozgałęzienie dla każdego obiektu iterowalnego.

cells = [(row, col) for row in range(3) if row % 2 == 0
         for col in range(2) if col % 2 == 0]
print(cells)
# [(0, 0), (2, 0)]

notacja włączania zbiorów(Set comprehensions)

Zamiana nawiasów kwadratowych [] w notacji list comprehension na nawiasy klamrowe {} tworzy zestaw (obiekt typu set).

{Expression for Any Variable Name in Iterable Object}
s = {i**2 for i in range(5)}

print(s)
# {0, 1, 4, 9, 16}

notacja włączenia słownika(Dict comprehensions)

Słowniki (obiekty typu dict) mogą być również generowane za pomocą notacji comprehension.

{}, a klucz i wartość w części wyrażeniowej określ jako klucz: wartość.

{Key: Value for Any Variable Name in Iterable Object}

Dla klucza i wartości można podać dowolne wyrażenie.

l = ['Alice', 'Bob', 'Charlie']

d = {s: len(s) for s in l}
print(d)
# {'Alice': 5, 'Bob': 3, 'Charlie': 7}

Aby utworzyć nowy słownik z listy kluczy i wartości, użyj funkcji zip().

keys = ['k1', 'k2', 'k3']
values = [1, 2, 3]

d = {k: v for k, v in zip(keys, values)}
print(d)
# {'k1': 1, 'k2': 2, 'k3': 3}

typ generatora(Generator expressions)

Jeśli nawiasy kwadratowe [] w notacji list comprehensions zostaną użyte jako nawiasy okrągłe (), to zamiast tupli zwracany jest generator. Nazywa się to wyrażeniami generatora.

Przykład notacji list comprehension.

l = [i**2 for i in range(5)]

print(l)
# [0, 1, 4, 9, 16]

print(type(l))
# <class 'list'>

Przykład wyrażenia generatora. Jeśli wydrukujesz() generator w takiej postaci, nie wypisze on swojej zawartości, ale jeśli uruchomisz go z instrukcją for, możesz uzyskać jego zawartość.

g = (i**2 for i in range(5))

print(g)
# <generator object <genexpr> at 0x10af944f8>

print(type(g))
# <class 'generator'>

for i in g:
    print(i)
# 0
# 1
# 4
# 9
# 16

Wyrażenia generatora pozwalają również na warunkowe rozgałęzianie i zagnieżdżanie przy użyciu notacji if, jak również notacji list comprehension.

g_cells = ((row, col) for row in range(0, 3)
           for col in range(0, 2) if col == row)

print(type(g_cells))
# <class 'generator'>

for i in g_cells:
    print(i)
# (0, 0)
# (1, 1)

Na przykład, jeśli lista z dużą liczbą elementów jest generowana przy użyciu notacji list comprehension, a następnie zapętlana przy użyciu instrukcji for, lista zawierająca wszystkie elementy zostanie wygenerowana na początku, jeśli używana jest notacja list comprehension. Z drugiej strony, jeśli użyjemy wyrażenia generatora, za każdym razem, gdy pętla jest powtarzana, elementy są generowane jeden po drugim, zmniejszając w ten sposób ilość wykorzystywanej pamięci.

Jeżeli wyrażenie generatora jest jedynym argumentem funkcji, można pominąć nawiasy okrągłe ().

print(sum([i**2 for i in range(5)]))
# 30

print(sum((i**2 for i in range(5))))
# 30

print(sum(i**2 for i in range(5)))
# 30

Jeśli chodzi o szybkość przetwarzania, notacja list comprehension jest często szybsza niż notacja generatora, gdy wszystkie elementy są przetwarzane.

Jednakże, na przykład, gdy oceniamy za pomocą all() lub any(), wynik jest określany, gdy występuje false lub true, więc użycie wyrażeń generatora może być szybsze niż użycie notacji list comprehension.

Nie istnieje notacja rozumienia tupli, ale jeśli użyjesz wyrażenia generatora jako argumentu funkcji tuple(), możesz wygenerować tuple w notacji rozumienia.

t = tuple(i**2 for i in range(5))

print(t)
# (0, 1, 4, 9, 16)

print(type(t))
# <class 'tuple'>