Wyrażenia generatora a rozumienie listy

Kiedy należy używać wyrażeń generatora, a kiedy składanie list w Pythonie?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]
Author: Sнаđошƒаӽ, 2008-09-07

8 answers

John ' s answer is good (that list comprehensions are better when you want to iterate over something several times). Warto jednak również zauważyć, że należy użyć listy, jeśli chcesz użyć którejkolwiek z metod listy. Na przykład następujący kod nie będzie działał:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Zasadniczo, użyj wyrażenia generatora, jeśli wszystko, co robisz, to powtarzanie raz. Jeśli chcesz przechowywać i korzystać z wygenerowanych wyników, to prawdopodobnie lepiej będzie Ci zrozumieć listę.

Od wydajność jest najczęstszym powodem, aby wybrać jeden nad drugim, moja rada jest, aby nie martwić się o to i po prostu wybrać jeden; jeśli okaże się, że twój program działa zbyt wolno, to i tylko wtedy powinieneś wrócić i martwić się o strojenie kodu.

 237
Author: Eli Courtwright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-06 20:54:08

Iteracja nad wyrażeniem generatora lub listą zrobi to samo. Jednak zrozumienie listy najpierw utworzy całą listę w pamięci, podczas gdy wyrażenie generatora utworzy elementy w locie, więc będziesz mógł go używać dla bardzo dużych (a także nieskończonych!).

 150
Author: dF.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-04 11:26:27

Użyj list comprehensions, gdy wynik musi być wielokrotnie powtarzany lub gdy szybkość jest najważniejsza. Użyj wyrażeń generatora, gdzie zakres jest duży lub nieskończony.

 82
Author: John Millikin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-06 20:10:59

Ważne jest to, że zrozumienie listy tworzy nową listę. Generator tworzy obiekt iterowalny, który "filtruje" materiał źródłowy w locie, gdy zużywasz bity.

Wyobraź sobie, że masz plik dziennika 2TB o nazwie " hugefile.txt", i chcesz zawartość i długość dla wszystkich linii, które zaczynają się od słowa "wpis".

Więc spróbuj zacząć od napisania listy:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

To uwalnia cały plik, przetwarza każdą linię I przechowuje pasujące linie w tablicy. Tablica ta może więc zawierać do 2 TB zawartości. To dużo pamięci RAM i prawdopodobnie nie jest praktyczne dla Twoich celów.

Więc zamiast tego możemy użyć generatora, aby zastosować "filtr" do naszej zawartości. Żadne dane nie są faktycznie odczytywane, dopóki nie zaczniemy iteracji nad wynikiem.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Nie odczytano jeszcze ani jednej linijki z naszego pliku. W rzeczywistości, powiedzmy, że chcemy filtrować nasz wynik jeszcze dalej:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Nadal nic nie zostało przeczytane, ale określiliśmy teraz dwa generatory, które będą działać na naszych danych zgodnie z naszym życzeniem.

Zapisujemy nasze przefiltrowane linie do innego pliku:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

teraz odczytujemy plik wejściowy. Ponieważ nasza pętla for nadal żąda dodatkowych linii, generator long_entries wymaga linii z generatora entry_lines, zwracając tylko te, których długość jest większa niż 80 znaków. Z kolei generator entry_lines żąda linii (filtrowanych według wskazań) z iteratora logfile, który z kolei odczytuje plik.

Więc zamiast "pchania" danych do funkcji wyjściowej w postaci w pełni wypełnionej listy, dajesz funkcji wyjściowej sposób na "wyciąganie" danych tylko wtedy, gdy jest to potrzebne. Jest to w naszym przypadku znacznie bardziej wydajne, ale nie tak elastyczne. Generatory są jednokierunkowe, jednokierunkowe; dane z pliku dziennika, który przeczytaliśmy, są natychmiast odrzucane, więc nie możemy wrócić do poprzedniej linii. Z drugiej strony nie musimy się martwić o przechowywanie danych po ich zakończeniu.

 50
Author: tylerl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-04 09:14:57

Zaletą wyrażenia generatora jest to, że zużywa mniej pamięci, ponieważ nie tworzy całej listy na raz. Wyrażenia generatora są najlepiej używane, gdy lista jest pośrednikiem, na przykład sumując wyniki lub tworząc dict z wyników.

Na przykład:

sum(x*2 for x in xrange(256))

dict( ((k, some_func(k) for k in some_list_of_keys) )

Zaletą jest to, że lista nie jest w pełni wygenerowana, a tym samym zużywana jest niewielka ilość pamięci (i powinna być również szybsza)

Należy jednak używać list comprehensions, gdy pożądane produkt końcowy to lista. Nie będziesz zapisywać żadnego memeory używając wyrażeń generatora, ponieważ chcesz wygenerowaną listę. Zyskujesz również możliwość korzystania z dowolnej funkcji listy, takich jak sortowanie lub odwrócone.

Na przykład:

reversed( [x*2 for x in xrange(256)] )
 42
Author: Chuck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-27 14:55:52

Podczas tworzenia generatora z mutowalnego obiektu (np. listy) należy pamiętać, że generator będzie oceniany na podstawie stanu listy w czasie korzystania z generatora, a nie w czasie tworzenia generatora:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Jeśli istnieje jakakolwiek szansa, że Twoja lista zostanie zmodyfikowana (lub zmienny obiekt wewnątrz tej listy), ale potrzebujesz stanu przy tworzeniu generatora, musisz zamiast tego użyć rozumienia listy.

 9
Author: freaker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-12 22:21:00

Używam Hadoop Mincemeat module . Myślę, że jest to świetny przykład, aby wziąć pod uwagę:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Tutaj generator pobiera liczby z pliku tekstowego (tak dużego jak 15GB) i stosuje prostą matematykę na tych liczbach za pomocą Map-reduce Hadoop. Gdybym nie korzystał z funkcji yield, a zamiast tego z listy, obliczanie sum i średniej zajęłoby znacznie więcej czasu(nie wspominając o złożoności przestrzeni).

Hadoop jest doskonałym przykładem wykorzystania wszystkich zalety generatorów.

 5
Author: Murphy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-18 00:33:30

Czasami funkcja tee z itertools zwraca wiele iteratorów dla tego samego generatora, które mogą być używane niezależnie.

 3
Author: Jacob Rigby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-04 11:27:07