Szukaj…


Wprowadzenie

Wyrażenia listowe w Pythonie są zwięzłymi, składniowymi konstrukcjami. Można je wykorzystać do generowania list z innych list poprzez zastosowanie funkcji do każdego elementu na liście. Poniższa sekcja wyjaśnia i demonstruje użycie tych wyrażeń.

Składnia

  • [x + 1 dla x w (1, 2, 3)] # zrozumienie listy, daje [2, 3, 4]
  • (x + 1 dla x w (1, 2, 3)) # wyrażenie generatora, da 2, potem 3, a następnie 4
  • [x dla x w (1, 2, 3), jeśli x% 2 == 0] # zrozumienie listy z filtrem, daje [2]
  • [x + 1 jeśli x% 2 == 0 jeszcze x dla x w (1, 2, 3)] # rozumienie listy z trójką
  • [x + 1 jeśli x% 2 == 0 jeszcze x dla xw zakresie (-3,4) jeśli x> 0] # rozumienie listy z trójką i filtrowaniem
  • {x dla x w (1, 2, 2, 3)} # ustaw zrozumienie, daje {1, 2, 3}
  • {k: v dla k, v w [('a', 1), ('b', 2)]} # dict zrozumienie, daje {'a': 1, 'b': 2} (python 2.7+ i Tylko 3.0+)
  • [x + y dla x w [1, 2] dla y w [10, 20]] # Zagnieżdżone pętle, daje [11, 21, 12, 22]
  • [x + y dla x w [1, 2, 3] jeśli x> 2 dla y w [3, 4, 5]] # Warunek sprawdzony na 1. pętli
  • [x + y dla x w [1, 2, 3] dla y w [3, 4, 5] jeśli x> 2] # Warunek sprawdzony w 2. pętli dla
  • [x dla x w xrange (10) jeśli x% 2 == 0] # Warunek sprawdzony, jeśli liczby zapętlone są liczbami nieparzystymi

Uwagi

Rozumienia to konstrukcje składniowe, które definiują struktury danych lub wyrażenia charakterystyczne dla danego języka. Właściwe korzystanie ze zrozumień interpretuje je na łatwo zrozumiałe wyrażenia. Jako wyrażenia można ich użyć:

  • po prawej stronie zadań
  • jako argumenty wywołań funkcji
  • w ciele funkcji lambda
  • jako samodzielne instrukcje. (Na przykład: [print(x) for x in range(10)] )

Zrozumienie listy

Zrozumienie listy tworzy nową list poprzez zastosowanie wyrażenia do każdego elementu iterowalnego . Najbardziej podstawowa forma to:

[ <expression> for <element> in <iterable> ]

Istnieje również opcjonalny warunek „jeśli”:

[ <expression> for <element> in <iterable> if <condition> ]

Każde <element> w <iterable> jest podłączone do <expression> jeśli (opcjonalnie) <condition> wartość true . Wszystkie wyniki są zwracane jednocześnie na nowej liście. Wyrażenia generatora są oceniane leniwie, ale wyrażenia listy oceniają natychmiast cały iterator - zużywając pamięć proporcjonalną do długości iteratora.

Aby utworzyć list kwadratowych liczb całkowitych:

squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]

Wyrażenie for ustawia x dla każdej wartości kolejno od (1, 2, 3, 4) . Wynik wyrażenia x * x jest dołączany do wewnętrznej list . Po zakończeniu list wewnętrzna jest przypisana do squares zmiennych.

Oprócz zwiększenia prędkości (jak wyjaśniono tutaj ), rozumienie listy jest w przybliżeniu równoważne z następującą pętlą for:

squares = []
for x in (1, 2, 3, 4):
    squares.append(x * x)
# squares: [1, 4, 9, 16]

Wyrażenie zastosowane do każdego elementu może być tak złożone, jak to konieczne:

# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']

# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']

# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']

jeszcze

else konstrukcjach ze zrozumieniem listy można stosować inne, ale należy zachować ostrożność w zakresie składni. Klauzule if / else należy stosować przed for pętli, a nie po:

# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']

[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax

# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']

Zauważ, że używa innej konstrukcji języka, wyrażenia warunkowego , które samo w sobie nie jest częścią składni zrozumienia . Natomiast if po for…in jest częścią wyliczeń list i służy do filtrowania elementów ze źródła iterowalnego.


Podwójna iteracja

Kolejność podwójnej iteracji [... for x in ... for y in ...] jest albo naturalna, albo sprzeczna z intuicją. Zasadą jest, aby przestrzegać odpowiednik for pętli:

def foo(i):
    return i, i + 0.5

for i in range(3):
    for x in foo(i):
        yield str(x)

Staje się to:

[str(x)
    for i in range(3)
        for x in foo(i)
]

Można to skompresować do jednej linii jako [str(x) for i in range(3) for x in foo(i)]


Mutacja w miejscu i inne skutki uboczne

Przed użyciem listowych, zrozumieć różnicę między funkcjami wezwała do ich skutków ubocznych (mutacji, albo w miejscu funkcji), które zwykle zwracają None i funkcje, które zwracają ciekawą wartość.

Wiele funkcji (szczególnie czystych ) po prostu bierze obiekt i zwraca jakiś obiekt. Funkcja na miejscu modyfikuje istniejący obiekt, który nazywa się efektem ubocznym . Inne przykłady obejmują operacje wejścia i wyjścia, takie jak drukowanie.

list.sort() sortuje listę w miejscu (co oznacza, że modyfikuje oryginalną listę) i zwraca wartość None . Dlatego nie będzie działać zgodnie z oczekiwaniami w rozumieniu listy:

[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]

Zamiast tego sorted() zwraca posortowaną list zamiast sortowania w miejscu:

[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]

Możliwe jest użycie rozumienia działań niepożądanych, takich jak operacje we / wy lub funkcje lokalne. Jednak pętla for jest zwykle bardziej czytelna. Chociaż działa to w Pythonie 3:

[print(x) for x in (1, 2, 3)]

Zamiast tego użyj:

for x in (1, 2, 3):
    print(x)

W niektórych sytuacjach funkcje skutków ubocznych odpowiednie do zrozumienia listy. random.randrange() ma efekt uboczny polegający na zmianie stanu generatora liczb losowych, ale zwraca również interesującą wartość. Dodatkowo, next() można wywołać na iteratorze.

Poniższy generator wartości losowych nie jest czysty, ale ma sens, ponieważ generator losowy jest resetowany za każdym razem, gdy obliczane jest wyrażenie:

from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]

Białe spacje w opisach list

Bardziej skomplikowane zestawienia list mogą osiągnąć niepożądaną długość lub stać się mniej czytelne. Chociaż mniej powszechne w przykładach, możliwe jest podzielenie zrozumienia listy na wiele linii, takich jak:

[
    x for x
    in 'foo'
    if x not in 'bar'
]

Słownik pojęć

Zrozumienie słownika jest podobne do rozumienia listy, z tym wyjątkiem, że tworzy obiekt słownika zamiast listy.

Podstawowy przykład:

Python 2.x 2.7
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}

który jest po prostu innym sposobem pisania:

dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}

Podobnie jak w przypadku rozumienia listy, możemy użyć wyrażenia warunkowego wewnątrz rozumienia dict, aby wytworzyć tylko elementy dict spełniające pewne kryteria.

Python 2.x 2.7
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}  
# Out: {'Exchange': 8, 'Overflow': 8}

Lub przepisane przy użyciu wyrażenia generatora.

dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}

Zaczynając od słownika i używając rozumienia słownika jako filtra par klucz-wartość

Python 2.x 2.7
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}

Przełączanie klucza i wartości słownika (słownik odwrócony)

Jeśli masz słownik zawierający proste wartości haszujące (zduplikowane wartości mogą mieć nieoczekiwane wyniki):

my_dict = {1: 'a', 2: 'b', 3: 'c'}

i chciałeś zamienić klucze i wartości, możesz zastosować kilka podejść w zależności od stylu kodowania:

  • swapped = {v: k for k, v in my_dict.items()}
  • swapped = dict((v, k) for k, v in my_dict.iteritems())
  • swapped = dict(zip(my_dict.values(), my_dict))
  • swapped = dict(zip(my_dict.values(), my_dict.keys()))
  • swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Python 2.x 2.3

Jeśli twój słownik jest duży, rozważ import itertools i izip lub imap .


Scalanie słowników

Połącz słowniki i opcjonalnie zastąp stare wartości za pomocą zagnieżdżonego słownika.

dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}

{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}

Preferowane może być jednak rozpakowywanie słownika ( PEP 448 ).

Python 3.x 3.5
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}

Uwaga : w języku Python 3.0 dodano tłumaczenia słowników i przeniesiono je do wersji 2.7+, w przeciwieństwie do list, które dodano w wersji 2.0. Wersje <2.7 mogą korzystać z wyrażeń generatora i wbudowanej funkcji dict() celu symulacji zachowania słowników.

Wyrażenia generatora

Wyrażenia generatora są bardzo podobne do wyrażeń listowych. Główną różnicą jest to, że nie tworzy jednocześnie pełnego zestawu wyników; tworzy obiekt generatora, który można następnie iterować.

Na przykład zobacz różnicę w następującym kodzie:

# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Python 2.x 2.4
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>

Są to dwa bardzo różne obiekty:

  • zrozumienie listy zwraca obiekt list podczas gdy zrozumienie generatora zwraca generator .

  • obiektów generator nie można indeksować i wykorzystuje next funkcję do porządkowania elementów.

Uwaga : Używamy xrange ponieważ on również tworzy obiekt generatora. Gdybyśmy użyli zakresu, utworzono by listę. xrange istnieje również tylko w późniejszej wersji Pythona 2. W Pythonie 3 range zwraca tylko generator. Aby uzyskać więcej informacji, zobacz przykład Różnice między funkcjami zakresu i Xrange .


Python 2.x 2.4
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'

g.next()  # 0
g.next()  # 1
g.next()  # 4
...
g.next()  # 81

g.next()  # Throws StopIteration Exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Python 3.x 3.0

UWAGA: Funkcja g.next() powinna zostać zastąpiona przez next(g) i xrange z range ponieważ Iterator.next() i xrange() nie istnieją w Pythonie 3.


Chociaż oba można powtórzyć w podobny sposób:

for i in [x**2 for x in range(10)]:
    print(i)

"""
Out:
0
1
4
...
81
"""
Python 2.x 2.4
for i in (x**2 for x in xrange(10)):
    print(i)

"""
Out:
0
1
4
.
.
.
81
"""

Przypadków użycia

Wyrażenia generatora są leniwie oceniane, co oznacza, że generują i zwracają każdą wartość tylko wtedy, gdy generator jest iterowany. Jest to często przydatne podczas iteracji po dużych zestawach danych, unikając konieczności tworzenia duplikatu zestawu danych w pamięci:

for square in (x**2 for x in range(1000000)):
    #do something

Innym typowym przypadkiem użycia jest unikanie iteracji po całej iteracji, jeśli nie jest to konieczne. W tym przykładzie element jest pobierany ze zdalnego interfejsu API przy każdej iteracji get_objects() . Tysiące obiektów mogą istnieć, należy je wyszukiwać jeden po drugim, a my musimy tylko wiedzieć, czy istnieje obiekt pasujący do wzorca. Używając wyrażenia generatora, gdy napotkamy obiekt pasujący do wzorca.

def get_objects():
    """Gets objects from an API one by one"""
    while True:
        yield get_next_item()

def object_matches_pattern(obj):
    # perform potentially complex calculation
    return matches_pattern

def right_item_exists():
    items = (object_matched_pattern(each) for each in get_objects())
    for item in items:
        if item.is_the_right_one:


            return True
    return False

Ustaw zrozumienie

Rozumienie zestawu jest podobne do rozumienia listy i słownika , ale tworzy zestaw , który jest nieuporządkowaną kolekcją unikalnych elementów.

Python 2.x 2.7
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}

# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}

# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
#           'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])

Demo na żywo

Pamiętaj, że zestawy są nieuporządkowane. Oznacza to, że kolejność wyników w zestawie może różnić się od przedstawionej w powyższych przykładach.

Uwaga : Zestaw interpretacji jest dostępny od Pythona 2.7+, w przeciwieństwie do list, które zostały dodane w wersji 2.0. W Python od 2.2 do Python 2.6 funkcja set() może być używana z wyrażeniem generatora w celu uzyskania tego samego wyniku:

Python 2.x 2.2
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}

Unikaj powtarzalnych i kosztownych operacji przy użyciu klauzuli warunkowej

Rozważ poniższe rozumienie listy:

>>> def f(x):
...     import time
...     time.sleep(.1)       # Simulate expensive function
...     return x**2

>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]

Powoduje to dwa wywołania do f(x) dla 1000 wartości x : jedno wywołanie do wygenerowania wartości, a drugie do sprawdzenia warunku if . Jeśli f(x) jest szczególnie kosztowną operacją, może to mieć znaczący wpływ na wydajność. Co gorsza, jeśli wywołanie f() ma skutki uboczne, może mieć zaskakujące wyniki.

Zamiast tego powinieneś ocenić kosztowną operację tylko raz dla każdej wartości x , generując iterację pośrednią ( wyrażenie generatora ) w następujący sposób:

>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]

Lub używając wbudowanego odpowiednika mapy :

>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]

Innym sposobem, który może dać bardziej czytelny kod, jest umieszczenie wyniku częściowego ( v w poprzednim przykładzie) w iterowalnym (takim jak lista lub krotka), a następnie iteracja nad nim. Ponieważ v będzie jedynym elementem iterowalnym, wynik jest taki, że teraz mamy odwołanie do wyniku naszej funkcji spowolnienia obliczonego tylko raz:

>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]

Jednak w praktyce logika kodu może być bardziej skomplikowana i ważne jest, aby był czytelny. Zasadniczo zalecana jest osobna funkcja generatora w stosunku do złożonej jednowarstwowej:

>>> def process_prime_numbers(iterable):
...     for x in iterable:
...         if is_prime(x):
...             yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]

Innym sposobem zapobiegania obliczeniowej f(x) wielokrotnego jest zastosowanie @functools.lru_cache() (Python 3.2 lub) dekorator na f(x) . W ten sposób, ponieważ wynik f dla danych wejściowych x został już raz obliczony, druga funkcja wywołująca odczytanie oryginalnej listy będzie tak szybka jak wyszukiwanie w słowniku. Podejście to wykorzystuje memoizację w celu poprawy wydajności, co jest porównywalne z użyciem wyrażeń generatora.


Powiedz, że musisz spłaszczyć listę

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

Niektóre z metod mogą być:

reduce(lambda x, y: x+y, l)

sum(l, [])

list(itertools.chain(*l))

Jednak zrozumienie listy zapewniłoby najlepszą złożoność czasu.

[item for sublist in l for item in sublist]

Skróty oparte na + (w tym dorozumiane użycie w sumie) są z konieczności O (L ^ 2), gdy istnieją L listy podrzędne - ponieważ lista wyników pośrednich jest coraz dłuższa, z każdym krokiem otrzymuje nowy obiekt listy wyników pośrednich przydzielone, a wszystkie pozycje z poprzedniego wyniku pośredniego muszą zostać skopiowane (a także kilka nowych na końcu). Tak więc (dla uproszczenia i bez faktycznej utraty ogólności) powiedzmy, że masz L podlisty I elementów każdy: pierwsze I elementy są kopiowane tam iz powrotem L-1 razy, drugie I elementy L-2 i tak dalej; całkowita liczba kopii jest I razy suma x dla x od 1 do L wykluczone, tj. I * (L ** 2) / 2.

Zrozumienie listy generuje tylko jedną listę, raz i kopiuje każdy element (z pierwotnego miejsca zamieszkania do listy wyników) również dokładnie raz.

Zrozumienia dotyczące krotek

Klauzula for zrozumienia listy może określać więcej niż jedną zmienną:

[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]

[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]

Jest to tak samo jak w for pętli:

for x, y in [(1,2), (3,4), (5,6)]:
    print(x+y)
# 3
# 7
# 11

Zauważ jednak, że jeśli wyrażenie rozpoczynające rozumienie jest krotką, należy je nawiasować w nawiasach:

[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax

[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]

Liczenie zdarzeń za pomocą rozumienia

Kiedy chcemy policzyć liczbę elementów w iterowalnym, spełniających pewne warunki, możemy użyć zrozumienia, aby utworzyć idiomatyczną składnię:

# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
    1 for x in range(1000) 
    if x % 2 == 0 and
    '9' in str(x)
))
# Out: 95

Podstawową koncepcję można podsumować jako:

  1. Iteruj po elementach z range(1000) .
  2. Złączyć wszystkie potrzebne if warunkach.
  3. Użyj 1 jako wyrażenia, aby zwrócić 1 dla każdego elementu, który spełnia warunki.
  4. Zsumuj wszystkie 1 s, aby określić liczbę elementów spełniających warunki.

Uwaga: W tym miejscu nie są zbierając 1 s na liście (zauważ brak nawiasach kwadratowych), ale przechodzą te bezpośrednio do sum funkcji, która jest podsumowującej je. Nazywa się to wyrażeniem generatora , które jest podobne do zrozumienia.

Zmiana typów na liście

Dane ilościowe są często odczytywane jako ciągi znaków, które przed przetworzeniem muszą zostać przekonwertowane na typy numeryczne. Typy wszystkich elementów listy można konwertować za pomocą funkcji Zrozumienie listy lub funkcji map() .

# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]

# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0] 


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow