Jest to raczej odwrotność do czego można używać funkcji generatora w języku Python? : generatory Pythona, wyrażenia generatora i itertools
moduł to niektóre z moich ulubionych funkcji w dzisiejszym Pythonie. Są szczególnie przydatne podczas konfigurowania łańcuchów operacji do wykonania na dużym stosie danych - często używam ich podczas przetwarzania plików DSV.
Kiedy więc nie jest dobry moment na użycie generatora, wyrażenia generatora lub itertools
funkcji?
- Kiedy wolę
zip()
więcejitertools.izip()
, lub range()
ponadxrange()
lub[x for x in foo]
koniec(x for x in foo)
?
Oczywiście ostatecznie musimy „rozłożyć” generator na rzeczywiste dane, zwykle tworząc listę lub iterując po niej pętlą nie będącą generatorem. Czasami wystarczy znać długość. Nie o to proszę.
Używamy generatorów, aby nie przypisywać nowych list do pamięci dla danych tymczasowych. Ma to sens zwłaszcza w przypadku dużych zbiorów danych. Czy ma to sens także w przypadku małych zbiorów danych? Czy występuje zauważalny kompromis między pamięcią a procesorem?
Jestem szczególnie zainteresowany, jeśli ktoś zrobił jakieś profilowanie na ten temat, w świetle otwierającej oczy dyskusji na temat wydajności rozumienia list w porównaniu z mapą () i filtrem () . ( link alternatywny )
źródło
<5
.Odpowiedzi:
Użyj listy zamiast generatora, gdy:
1) Musisz wielokrotnie uzyskiwać dostęp do danych (tj. Buforować wyniki zamiast ponownie je obliczać):
for i in outer: # used once, okay to be a generator or return a list for j in inner: # used multiple times, reusing a list is better ...
2) Potrzebujesz dostępu swobodnego (lub dowolnego dostępu innego niż kolejność do przodu):
for i in reversed(data): ... # generators aren't reversible s[i], s[j] = s[j], s[i] # generators aren't indexable
3) Musisz połączyć ciągi znaków (co wymaga dwóch przejść przez dane):
s = ''.join(data) # lists are faster than generators in this use case
4) Używasz PyPy, które czasami nie może zoptymalizować kodu generatora tak bardzo, jak przy normalnych wywołaniach funkcji i manipulowaniu listami.
źródło
ireduce
do replikacji sprzężenia?''.join('%s' % i for i in xrange(10))
Ogólnie rzecz biorąc, nie używaj generatora, gdy potrzebujesz operacji listowych, takich jak len (), reverse () i tak dalej.
Może się również zdarzyć, że nie chcesz leniwej oceny (np. Wykonanie wszystkich obliczeń z góry, abyś mógł zwolnić zasób). W takim przypadku wyrażenie listy może być lepsze.
źródło
Profil, profil, profil.
Profilowanie kodu to jedyny sposób, aby dowiedzieć się, czy to, co robisz, ma jakikolwiek wpływ.
Większość zastosowań xrange, generatorów itp. Dotyczy rozmiaru statycznego, małych zbiorów danych. Dopiero gdy uzyskasz duże zbiory danych, to naprawdę robi różnicę. range () vs. xrange () to głównie kwestia tego, aby kod wyglądał odrobinę bardziej brzydko i niczego nie tracił, a może coś zyskał.
Profil, profil, profil.
źródło
Nigdy nie powinno się faworyzować
zip
naizip
,range
nadxrange
lub listowych ponad listowe generatora. W Pythonie 3.0range
maxrange
semantykę podobną do-i semantykę podobną dozip
maizip
.Listy składane są w rzeczywistości bardziej przejrzyste, jak wtedy,
list(frob(x) for x in foo)
gdy potrzebujesz rzeczywistej listy.źródło
for
pętle!), Ale łatwo można napisać niezrozumiałe listy składane.[]
formularz jest wystarczająco opisowy (i ogólnie bardziej zwięzły i mniej zagracony). Ale to tylko kwestia gustu.Jak wspomniałeś, „To ma sens zwłaszcza w przypadku dużych zbiorów danych”, myślę, że to odpowiada na twoje pytanie.
Jeśli nie uderzasz w żadne ściany, jeśli chodzi o wydajność, nadal możesz trzymać się list i standardowych funkcji. Następnie, gdy napotkasz problemy z wydajnością, dokonaj zmiany.
Jak wspomniał @ u0b34a0f6ae w komentarzach, jednak użycie generatorów na początku może ułatwić skalowanie do większych zbiorów danych.
źródło
Jeśli chodzi o wydajność: jeśli używasz psyco, listy mogą być nieco szybsze niż generatory. W poniższym przykładzie listy są prawie 50% szybsze przy użyciu psyco.full ()
import psyco import time import cStringIO def time_func(func): """The amount of time it requires func to run""" start = time.clock() func() return time.clock() - start def fizzbuzz(num): """That algorithm we all know and love""" if not num % 3 and not num % 5: return "%d fizz buzz" % num elif not num % 3: return "%d fizz" % num elif not num % 5: return "%d buzz" % num return None def with_list(num): """Try getting fizzbuzz with a list comprehension and range""" out = cStringIO.StringIO() for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]: print >> out, fibby return out.getvalue() def with_genx(num): """Try getting fizzbuzz with generator expression and xrange""" out = cStringIO.StringIO() for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)): print >> out, fibby return out.getvalue() def main(): """ Test speed of generator expressions versus list comprehensions, with and without psyco. """ #our variables nums = [10000, 100000] funcs = [with_list, with_genx] # try without psyco 1st print "without psyco" for num in nums: print " number:", num for func in funcs: print func.__name__, time_func(lambda : func(num)), "seconds" print # now with psyco print "with psyco" psyco.full() for num in nums: print " number:", num for func in funcs: print func.__name__, time_func(lambda : func(num)), "seconds" print if __name__ == "__main__": main()
Wyniki:
without psyco number: 10000 with_list 0.0519102208309 seconds with_genx 0.0535933367509 seconds number: 100000 with_list 0.542204280744 seconds with_genx 0.557837353115 seconds with psyco number: 10000 with_list 0.0286369007033 seconds with_genx 0.0513424889137 seconds number: 100000 with_list 0.335414877839 seconds with_genx 0.580363490491 seconds
źródło
Jeśli chodzi o wydajność, nie przychodzi mi do głowy żaden moment, w którym chciałbyś użyć listy zamiast generatora.
źródło
all(True for _ in range(10 ** 8))
jest wolniejszy niżall([True for _ in range(10 ** 8)])
w Pythonie 3.8. Wolałbym listę niż generator tutajNigdy nie znalazłem sytuacji, w której generatory utrudniałyby to, co próbujesz zrobić. Jest jednak wiele przypadków, w których użycie generatorów nie pomogłoby bardziej niż ich nieużywanie.
Na przykład:
sorted(xrange(5))
Nie oferuje żadnej poprawy w stosunku do:
sorted(range(5))
źródło
range(5)
, ponieważ wynikowa lista jest już posortowana.Powinieneś preferować wyrażenia listowe, jeśli chcesz później zachować wartości dla czegoś innego, a rozmiar twojego zestawu nie jest zbyt duży.
Na przykład: tworzysz listę, którą będziesz powtarzać kilka razy później w swoim programie.
Do pewnego stopnia można myśleć o generatorach jako o zamienniku dla iteracji (pętli), a o listach składanych jako o typie inicjalizacji struktury danych. Jeśli chcesz zachować strukturę danych, użyj wyrażeń listowych.
źródło
itertools.tee()
może ci pomóc. Ale ogólnie, jeśli chcesz więcej niż jeden przebieg lub losowy dostęp do niektórych danych pośrednich, zrób ich listę / ustaw / dyktuj.