Zaczynam uczyć się Pythona i natknąłem się na funkcje generatora, te, które zawierają w sobie instrukcję return. Chcę wiedzieć, jakie rodzaje problemów te funkcje są naprawdę dobre w rozwiązywaniu.
Generatory dają leniwą ocenę. Używasz ich, iterując nad nimi, albo jawnie z „for”, albo pośrednio, przekazując je do dowolnej funkcji lub konstrukcji, która się iteruje. Możesz myśleć o generatorach jako o zwracaniu wielu elementów, tak jakby zwracały one listę, ale zamiast zwracać je wszystkie naraz, zwracają je jeden po drugim, a funkcja generatora jest wstrzymywana do momentu żądania następnego elementu.
Generatory są dobre do obliczania dużych zestawów wyników (w szczególności obliczeń obejmujących same pętle), w których nie wiesz, czy będziesz potrzebować wszystkich wyników lub gdzie nie chcesz przydzielić pamięci dla wszystkich wyników w tym samym czasie . Lub w sytuacjach, gdy generator korzysta z innego generatora lub zużywa inne zasoby, a wygodniej jest, jeśli dzieje się to tak późno, jak to możliwe.
Innym zastosowaniem generatorów (tak naprawdę jest to samo) jest zastąpienie wywołań zwrotnych iteracją. W niektórych sytuacjach chcesz, aby funkcja wykonała dużo pracy i od czasu do czasu zgłaszała się do osoby dzwoniącej. Tradycyjnie używałbyś do tego funkcji wywołania zwrotnego. Przekazujesz to wywołanie zwrotne do funkcji pracy i okresowo wywołuje to wywołanie zwrotne. Podejście oparte na generatorze polega na tym, że funkcja pracy (obecnie generator) nie wie nic o wywołaniu zwrotnym i po prostu ustępuje, gdy chce coś zgłosić. Program wywołujący, zamiast pisać osobne wywołanie zwrotne i przekazywać je do funkcji pracy, wykonuje wszystkie czynności raportowania w małej pętli „za” wokół generatora.
Załóżmy na przykład, że napisałeś program „wyszukiwania systemu plików”. Możesz przeprowadzić wyszukiwanie w całości, zebrać wyniki, a następnie wyświetlić je pojedynczo. Wszystkie wyniki musiałyby zostać zebrane przed pokazaniem pierwszego, a wszystkie wyniki byłyby w tym samym czasie w pamięci. Możesz też wyświetlać wyniki podczas ich wyszukiwania, co byłoby bardziej wydajne pod względem pamięci i bardziej przyjazne dla użytkownika. To ostatnie można wykonać, przekazując funkcję drukowania wyników do funkcji wyszukiwania systemu plików, lub można to zrobić, po prostu czyniąc funkcję wyszukiwania generatorem i iterując wynik.
Jeśli chcesz zobaczyć przykład dwóch ostatnich podejść, zobacz os.path.walk () (stara funkcja chodzenia po systemie plików z wywołaniem zwrotnym) i os.walk () (nowy generator chodzenia po systemie plików). Oczywiście, jeśli naprawdę chciałeś zebrać wszystkie wyniki na liście, podejście generatora jest trywialne, aby przekonwertować je na podejście z dużą listą:
Czy generator, taki jak ten, który tworzy listy systemów plików, wykonuje działania równolegle z kodem, który uruchamia ten generator w pętli? Idealnie byłoby, gdyby komputer uruchomił ciało pętli (przetwarzając ostatni wynik), jednocześnie wykonując wszystko, co generator musi zrobić, aby uzyskać następną wartość.
Steven Lu
@StevenLu: O ile nie ma problemu z ręcznym uruchomieniem wątków przed yieldi joinpo nich, aby uzyskać następny wynik, nie jest on wykonywany równolegle (i nie robi tego żaden standardowy generator bibliotek; potajemnie uruchamiane wątki są odrzucone). Generator zatrzymuje się przy każdym, yieldaż zostanie zażądana następna wartość. Jeśli generator pakuje operacje we / wy, system operacyjny może proaktywnie buforować dane z pliku przy założeniu, że zostanie wkrótce o to poproszony, ale taki jest system operacyjny, w którym nie jest zaangażowany Python.
ShadowRanger
90
Jednym z powodów użycia generatora jest uczynienie rozwiązania bardziej zrozumiałym dla niektórych rozwiązań.
Drugi polega na traktowaniu wyników pojedynczo, unikając tworzenia ogromnych list wyników, które i tak byłyby przetwarzane oddzielnie.
Jeśli masz funkcję Fibonacciego-up-to-n:
# function versiondef fibon(n):
a = b =1
result =[]for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
Możesz łatwiej napisać funkcję w ten sposób:
# generator versiondef fibon(n):
a = b =1for i in xrange(n):yield a
a, b = b, a + b
Funkcja jest bardziej przejrzysta. A jeśli użyjesz takiej funkcji:
for x in fibon(1000000):print x,
w tym przykładzie, jeśli używasz wersji generatora, cała lista 1000000 pozycji nie zostanie utworzona, tylko jedna wartość na raz. Nie byłoby tak w przypadku korzystania z wersji listy, w której lista byłaby tworzona jako pierwsza.
Nieoczywistym zastosowaniem generatorów jest tworzenie funkcji przerywalnych, które umożliwiają wykonywanie takich czynności, jak aktualizacja interfejsu użytkownika lub uruchamianie kilku zadań „jednocześnie” (w rzeczywistości z przeplotem), bez korzystania z wątków.
Sekcja Motywacja jest ładna, ponieważ ma konkretny przykład: „Gdy funkcja producenta ma wystarczająco ciężką pracę, która wymaga utrzymania stanu między wygenerowanymi wartościami, większość języków programowania nie oferuje przyjemnego i wydajnego rozwiązania poza dodaniem funkcji wywołania zwrotnego do argumentu producenta lista ... Na przykład tokenize.py w standardowej bibliotece przyjmuje to podejście ”
Ben Creasy 16.04.16
38
Znajduję to wyjaśnienie, które rozwiewa moje wątpliwości. Ponieważ istnieje możliwość, że osoba, która nie wie, Generatorsrównież nie wie o tymyield
Powrót
Instrukcja return to miejsce, w którym wszystkie zmienne lokalne są niszczone, a wynikowa wartość jest zwracana (zwracana) programowi wywołującemu. Jeśli jakiś czas później zostanie wywołana ta sama funkcja, funkcja otrzyma nowy zestaw zmiennych.
Wydajność
Ale co, jeśli zmienne lokalne nie zostaną wyrzucone po wyjściu z funkcji? Oznacza to, że możemy resume the functiontam, gdzie przerwaliśmy. W tym miejscu wprowadzana jest koncepcja, generatorsa yieldstwierdzenie zostaje wznowione tam, gdzie functionzostało przerwane.
def generate_integers(N):for i in xrange(N):yield i
In[1]: gen = generate_integers(3)In[2]: gen
<generator object at 0x8117f90>In[3]: gen.next()0In[4]: gen.next()1In[5]: gen.next()
Na tym polega różnica między returni yieldinstrukcjami w Pythonie.
Instrukcja Yield sprawia, że funkcja jest funkcją generatora.
Generatory są więc prostym i wydajnym narzędziem do tworzenia iteratorów. Są napisane jak zwykłe funkcje, ale używają yieldinstrukcji, ilekroć chcą zwrócić dane. Za każdym razem, gdy wywoływana jest funkcja next (), generator wznawia pracę od miejsca, w którym został przerwany (zapamiętuje wszystkie wartości danych i ostatnią instrukcję).
Załóżmy, że masz 100 milionów domen w tabeli MySQL i chcesz zaktualizować pozycję Alexa dla każdej domeny.
Pierwszą rzeczą, której potrzebujesz, jest wybranie nazw domen z bazy danych.
Powiedzmy, że twoja nazwa tabeli to domainsi nazwa kolumny domain.
Jeśli użyjesz SELECT domain FROM domains, zwróci 100 milionów wierszy, co zużyje dużo pamięci. Twój serwer może ulec awarii.
Zdecydowałeś się więc uruchomić program partiami. Powiedzmy, że nasz rozmiar partii to 1000.
W naszej pierwszej partii sprawdzimy pierwsze 1000 wierszy, sprawdzimy pozycję Alexa dla każdej domeny i zaktualizujemy wiersz bazy danych.
W naszej drugiej partii będziemy pracować nad następnymi 1000 rzędami. W naszej trzeciej partii będzie to od 2001 do 3000 i tak dalej.
Teraz potrzebujemy funkcji generatora, która generuje nasze partie.
Oto nasza funkcja generatora:
defResultGenerator(cursor, batchsize=1000):whileTrue:
results = cursor.fetchmany(batchsize)ifnot results:breakfor result in results:yield result
Jak widać, nasza funkcja stale zapisuje yieldwyniki. Jeśli użyjesz słowa kluczowego returnzamiast yield, cała funkcja zostanie zakończona, gdy osiągnie wartość return.
return- returns only once
yield- returns multiple times
Jeśli funkcja używa słowa kluczowego, yieldto jest to generator.
Teraz możesz iterować w ten sposób:
db =MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")for result inResultGenerator(cursor):
doSomethingWith(result)
db.close()
byłoby bardziej praktyczne, gdyby wydajność można było wyjaśnić w kategoriach programowania rekurencyjnego / dynamicznego!
igaurav
27
Buforowanie Gdy efektywne jest pobieranie danych w dużych porcjach, ale przetwarzanie ich w małych porcjach, generator może pomóc:
def bufferedFetch():whileTrue:
buffer = getBigChunkOfData()# insert some code to break on 'end of data'for i in buffer:yield i
Powyższe pozwala łatwo oddzielić buforowanie od przetwarzania. Funkcja konsumenta może teraz tylko pobierać wartości jeden po drugim, nie martwiąc się o buforowanie.
Jeśli getBigChuckOfData nie jest leniwy, to nie rozumiem, jakie korzyści przynosi tutaj zysk. Jaki jest przypadek użycia tej funkcji?
Sean Geoffrey Pietz
1
Ale chodzi o to, że IIUC, buforowane pobieranie opóźnia połączenie z getBigChunkOfData. Jeśli getBigChunkOfData był już leniwy, wówczas buforowane pobieranie byłoby bezużyteczne. Każde wywołanie funkcji bufferedFetch () zwróci jeden element bufora, nawet jeśli BigChunk został już wczytany. I nie musisz jawnie liczyć następnego elementu, który ma zostać zwrócony, ponieważ mechanika wydajności robi to wprost niejawnie.
Hmijail opłakuje odrodzenie
21
Przekonałem się, że generatory są bardzo pomocne w czyszczeniu twojego kodu i dają ci bardzo unikalny sposób kapsułkowania i modularyzacji kodu. W sytuacji, gdzie trzeba coś stale wypluć wartości na podstawie własnego wewnętrznego przetwarzania A kiedy czegoś potrzebuje być wywoływana z dowolnego miejsca w kodzie (i nie tylko wewnątrz pętli lub bloku na przykład), generatory są cechą posługiwać się.
Abstrakcyjnym przykładem może być generator liczb Fibonacciego, który nie żyje w pętli, a gdy zostanie wywołany z dowolnego miejsca, zawsze zwróci następną liczbę w sekwencji:
def fib():
first =0
second =1yield first
yield second
while1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Teraz masz dwa obiekty generatora liczb Fibonacciego, które możesz wywoływać z dowolnego miejsca w kodzie i zawsze będą zwracać coraz większe liczby Fibonacciego w następujący sposób:
Zaletą generatorów jest to, że otaczają one stan bez konieczności przechodzenia przez obręcz tworzenia obiektów. Jednym ze sposobów myślenia o nich są „funkcje”, które zapamiętują ich stan wewnętrzny.
Mam przykład Fibonacciego z Python Generators - czym one są? a przy odrobinie wyobraźni możesz wymyślić wiele innych sytuacji, w których generatory stanowią doskonałą alternatywę dla forpętli i innych tradycyjnych konstrukcji iteracyjnych.
Przez większość czasu wszystkie elementy iterablenie muszą być od samego początku, ale można je generować w locie, gdy są potrzebne. W obu przypadkach może to być znacznie bardziej wydajne
przestrzeń (nigdy nie musisz przechowywać wszystkich przedmiotów jednocześnie) i
czas (iteracja może zakończyć się, zanim wszystkie elementy będą potrzebne).
Innym razem nie znasz nawet wszystkich przedmiotów przed czasem. Na przykład:
for command in user_input():
do_stuff_with(command)
Nie masz możliwości wcześniejszego poznania wszystkich poleceń użytkownika, ale możesz skorzystać z takiej ładnej pętli, jeśli generator generuje polecenia:
... i nieskończoną sekwencją może być sekwencja wygenerowana przez wielokrotne przewijanie krótkiej listy, powrót do początku po osiągnięciu końca. Używam tego do wybierania kolorów na wykresach lub do tworzenia zajętych pulsatorów lub pokręteł w tekście.
Moje ulubione zastosowania to operacje „filtrowania” i „zmniejszania”.
Powiedzmy, że czytamy plik i chcemy tylko linii zaczynających się od „##”.
def filter2sharps( aSequence ):for l in aSequence:if l.startswith("##"):yield l
Następnie możemy użyć funkcji generatora w odpowiedniej pętli
source= file(...)for line in filter2sharps( source.readlines()):print line
source.close()
Przykład redukcji jest podobny. Załóżmy, że mamy plik, w którym musimy zlokalizować bloki <Location>...</Location>linii. [Nie tagi HTML, ale linie wyglądające jak tagi].
def reduceLocation( aSequence ):
keep=False
block=Nonefor line in aSequence:if line.startswith("</Location"):
block.append( line )yield block
block=None
keep=Falseelif line.startsWith("<Location"):
block=[ line ]
keep=Trueelif keep:
block.append( line )else:passif block isnotNone:yield block # A partial block, icky
Ponownie możemy użyć tego generatora we właściwej pętli for.
source = file(...)for b in reduceLocation( source.readlines()):print b
source.close()
Chodzi o to, że funkcja generatora pozwala nam filtrować lub redukować sekwencję, tworząc kolejną sekwencję po jednej wartości na raz.
fileobj.readlines()czytałby cały plik do listy w pamięci, co przeczy celowi używania generatorów. Ponieważ obiekty plików są już iterowalne, możesz for b in your_generator(fileobject):zamiast tego użyć . W ten sposób plik będzie odczytywany jeden wiersz na raz, aby uniknąć odczytu całego pliku.
nosklo
redukcja lokalizacji jest dość dziwna, jeśli chodzi o tworzenie listy, dlaczego po prostu nie dać każdej linii? Filtruj i zmniejszaj również wbudowane funkcje z oczekiwanymi zachowaniami (zobacz pomoc w ipython itp.), Użycie „zmniejsz” jest takie samo jak filtrowanie.
James Antill,
Dobra uwaga na readlines (). Zwykle zdaję sobie sprawę, że pliki są pierwszorzędnymi iteratorami linii podczas testów jednostkowych.
S.Lott,
W rzeczywistości „redukcja” łączy wiele pojedynczych linii w obiekt złożony. Dobra, to lista, ale wciąż jest to redukcja zaczerpnięta ze źródła.
S.Lott,
9
Praktycznym przykładem, w którym można skorzystać z generatora jest, jeśli masz jakiś kształt i chcesz iterować po jego rogach, krawędziach itp. Do własnego projektu ( tutaj kod źródłowy ) miałem prostokąt:
Teraz mogę utworzyć prostokąt i zapętlić jego rogi:
myrect=Rect(50,50,100,100)for corner in myrect:print(corner)
Zamiast tego __iter__możesz mieć metodę iter_cornersi wywołać ją za pomocą for corner in myrect.iter_corners(). Jest po prostu bardziej elegancki w użyciu, __iter__ponieważ odtąd możemy używać nazwy instancji klasy bezpośrednio w forwyrażeniu.
Kilka dobrych odpowiedzi tutaj, ale polecam również pełną lekturę samouczka programowania funkcjonalnego Python, który pomaga wyjaśnić niektóre z bardziej potencjalnych przypadków użycia generatorów.
Ponieważ nie wspomniano o metodzie wysyłania generatora, oto przykład:
def test():for i in xrange(5):
val =yieldprint(val)
t = test()# Proceed to 'yield' statement
next(t)# Send value to yield
t.send(1)
t.send('2')
t.send([3])
Pokazuje możliwość wysłania wartości do działającego generatora. Bardziej zaawansowany kurs na temat generatorów w poniższym filmie (w tym yieldz eksploracji, generatorów do przetwarzania równoległego, przekraczania limitu rekurencji itp.)
Stosy rzeczy. Za każdym razem, gdy chcesz wygenerować sekwencję elementów, ale nie chcesz „zmaterializować” ich wszystkich na liście jednocześnie. Na przykład możesz mieć prosty generator, który zwraca liczby pierwsze:
def primes():
primes_found = set()
primes_found.add(2)yield2for i in itertools.count(1):
candidate = i *2+1ifnot all(candidate % prime for prime in primes_found):
primes_found.add(candidate)yield candidate
Następnie możesz użyć tego do wygenerowania produktów kolejnych liczb pierwszych:
def prime_products():
primeiter = primes()
prev = primeiter.next()for prime in primeiter:yield prime * prev
prev = prime
Są to dość trywialne przykłady, ale możesz zobaczyć, jak może być przydatne do przetwarzania dużych (potencjalnie nieskończonych!) Zestawów danych bez generowania ich z wyprzedzeniem, co jest tylko jednym z bardziej oczywistych zastosowań.
jeśli nie jakikolwiek (kandydat% prime na pierwszą w prime_found) powinien być jeśli wszyscy (kandydat% prime na pierwszą w prime_found)
rjmunro 19.09.08
Tak, chciałem napisać „jeśli nie dowolny (kandydat% prime == 0 dla liczby pierwszej w primes_found). Ale twoja jest nieco starsza. :)
Nick Johnson
Chyba zapomniałeś usunąć „nie” z jeśli nie wszystkich (kandydat% prime na prime w primes_found)
Thava
0
Nadaje się również do drukowania liczb pierwszych do n:
def genprime(n=10):for num in range(3, n+1):for factor in range(2, num):if num%factor ==0:breakelse:yield(num)for prime_num in genprime(100):print(prime_num)
Odpowiedzi:
Generatory dają leniwą ocenę. Używasz ich, iterując nad nimi, albo jawnie z „for”, albo pośrednio, przekazując je do dowolnej funkcji lub konstrukcji, która się iteruje. Możesz myśleć o generatorach jako o zwracaniu wielu elementów, tak jakby zwracały one listę, ale zamiast zwracać je wszystkie naraz, zwracają je jeden po drugim, a funkcja generatora jest wstrzymywana do momentu żądania następnego elementu.
Generatory są dobre do obliczania dużych zestawów wyników (w szczególności obliczeń obejmujących same pętle), w których nie wiesz, czy będziesz potrzebować wszystkich wyników lub gdzie nie chcesz przydzielić pamięci dla wszystkich wyników w tym samym czasie . Lub w sytuacjach, gdy generator korzysta z innego generatora lub zużywa inne zasoby, a wygodniej jest, jeśli dzieje się to tak późno, jak to możliwe.
Innym zastosowaniem generatorów (tak naprawdę jest to samo) jest zastąpienie wywołań zwrotnych iteracją. W niektórych sytuacjach chcesz, aby funkcja wykonała dużo pracy i od czasu do czasu zgłaszała się do osoby dzwoniącej. Tradycyjnie używałbyś do tego funkcji wywołania zwrotnego. Przekazujesz to wywołanie zwrotne do funkcji pracy i okresowo wywołuje to wywołanie zwrotne. Podejście oparte na generatorze polega na tym, że funkcja pracy (obecnie generator) nie wie nic o wywołaniu zwrotnym i po prostu ustępuje, gdy chce coś zgłosić. Program wywołujący, zamiast pisać osobne wywołanie zwrotne i przekazywać je do funkcji pracy, wykonuje wszystkie czynności raportowania w małej pętli „za” wokół generatora.
Załóżmy na przykład, że napisałeś program „wyszukiwania systemu plików”. Możesz przeprowadzić wyszukiwanie w całości, zebrać wyniki, a następnie wyświetlić je pojedynczo. Wszystkie wyniki musiałyby zostać zebrane przed pokazaniem pierwszego, a wszystkie wyniki byłyby w tym samym czasie w pamięci. Możesz też wyświetlać wyniki podczas ich wyszukiwania, co byłoby bardziej wydajne pod względem pamięci i bardziej przyjazne dla użytkownika. To ostatnie można wykonać, przekazując funkcję drukowania wyników do funkcji wyszukiwania systemu plików, lub można to zrobić, po prostu czyniąc funkcję wyszukiwania generatorem i iterując wynik.
Jeśli chcesz zobaczyć przykład dwóch ostatnich podejść, zobacz os.path.walk () (stara funkcja chodzenia po systemie plików z wywołaniem zwrotnym) i os.walk () (nowy generator chodzenia po systemie plików). Oczywiście, jeśli naprawdę chciałeś zebrać wszystkie wyniki na liście, podejście generatora jest trywialne, aby przekonwertować je na podejście z dużą listą:
źródło
yield
ijoin
po nich, aby uzyskać następny wynik, nie jest on wykonywany równolegle (i nie robi tego żaden standardowy generator bibliotek; potajemnie uruchamiane wątki są odrzucone). Generator zatrzymuje się przy każdym,yield
aż zostanie zażądana następna wartość. Jeśli generator pakuje operacje we / wy, system operacyjny może proaktywnie buforować dane z pliku przy założeniu, że zostanie wkrótce o to poproszony, ale taki jest system operacyjny, w którym nie jest zaangażowany Python.Jednym z powodów użycia generatora jest uczynienie rozwiązania bardziej zrozumiałym dla niektórych rozwiązań.
Drugi polega na traktowaniu wyników pojedynczo, unikając tworzenia ogromnych list wyników, które i tak byłyby przetwarzane oddzielnie.
Jeśli masz funkcję Fibonacciego-up-to-n:
Możesz łatwiej napisać funkcję w ten sposób:
Funkcja jest bardziej przejrzysta. A jeśli użyjesz takiej funkcji:
w tym przykładzie, jeśli używasz wersji generatora, cała lista 1000000 pozycji nie zostanie utworzona, tylko jedna wartość na raz. Nie byłoby tak w przypadku korzystania z wersji listy, w której lista byłaby tworzona jako pierwsza.
źródło
list(fibon(5))
Zobacz sekcję „Motywacja” w PEP 255 .
Nieoczywistym zastosowaniem generatorów jest tworzenie funkcji przerywalnych, które umożliwiają wykonywanie takich czynności, jak aktualizacja interfejsu użytkownika lub uruchamianie kilku zadań „jednocześnie” (w rzeczywistości z przeplotem), bez korzystania z wątków.
źródło
Znajduję to wyjaśnienie, które rozwiewa moje wątpliwości. Ponieważ istnieje możliwość, że osoba, która nie wie,
Generators
również nie wie o tymyield
Powrót
Instrukcja return to miejsce, w którym wszystkie zmienne lokalne są niszczone, a wynikowa wartość jest zwracana (zwracana) programowi wywołującemu. Jeśli jakiś czas później zostanie wywołana ta sama funkcja, funkcja otrzyma nowy zestaw zmiennych.
Wydajność
Ale co, jeśli zmienne lokalne nie zostaną wyrzucone po wyjściu z funkcji? Oznacza to, że możemy
resume the function
tam, gdzie przerwaliśmy. W tym miejscu wprowadzana jest koncepcja,generators
ayield
stwierdzenie zostaje wznowione tam, gdziefunction
zostało przerwane.Na tym polega różnica między
return
iyield
instrukcjami w Pythonie.Instrukcja Yield sprawia, że funkcja jest funkcją generatora.
Generatory są więc prostym i wydajnym narzędziem do tworzenia iteratorów. Są napisane jak zwykłe funkcje, ale używają
yield
instrukcji, ilekroć chcą zwrócić dane. Za każdym razem, gdy wywoływana jest funkcja next (), generator wznawia pracę od miejsca, w którym został przerwany (zapamiętuje wszystkie wartości danych i ostatnią instrukcję).źródło
Przykład ze świata rzeczywistego
Załóżmy, że masz 100 milionów domen w tabeli MySQL i chcesz zaktualizować pozycję Alexa dla każdej domeny.
Pierwszą rzeczą, której potrzebujesz, jest wybranie nazw domen z bazy danych.
Powiedzmy, że twoja nazwa tabeli to
domains
i nazwa kolumnydomain
.Jeśli użyjesz
SELECT domain FROM domains
, zwróci 100 milionów wierszy, co zużyje dużo pamięci. Twój serwer może ulec awarii.Zdecydowałeś się więc uruchomić program partiami. Powiedzmy, że nasz rozmiar partii to 1000.
W naszej pierwszej partii sprawdzimy pierwsze 1000 wierszy, sprawdzimy pozycję Alexa dla każdej domeny i zaktualizujemy wiersz bazy danych.
W naszej drugiej partii będziemy pracować nad następnymi 1000 rzędami. W naszej trzeciej partii będzie to od 2001 do 3000 i tak dalej.
Teraz potrzebujemy funkcji generatora, która generuje nasze partie.
Oto nasza funkcja generatora:
Jak widać, nasza funkcja stale zapisuje
yield
wyniki. Jeśli użyjesz słowa kluczowegoreturn
zamiastyield
, cała funkcja zostanie zakończona, gdy osiągnie wartość return.Jeśli funkcja używa słowa kluczowego,
yield
to jest to generator.Teraz możesz iterować w ten sposób:
źródło
Buforowanie Gdy efektywne jest pobieranie danych w dużych porcjach, ale przetwarzanie ich w małych porcjach, generator może pomóc:
Powyższe pozwala łatwo oddzielić buforowanie od przetwarzania. Funkcja konsumenta może teraz tylko pobierać wartości jeden po drugim, nie martwiąc się o buforowanie.
źródło
Przekonałem się, że generatory są bardzo pomocne w czyszczeniu twojego kodu i dają ci bardzo unikalny sposób kapsułkowania i modularyzacji kodu. W sytuacji, gdzie trzeba coś stale wypluć wartości na podstawie własnego wewnętrznego przetwarzania A kiedy czegoś potrzebuje być wywoływana z dowolnego miejsca w kodzie (i nie tylko wewnątrz pętli lub bloku na przykład), generatory są cechą posługiwać się.
Abstrakcyjnym przykładem może być generator liczb Fibonacciego, który nie żyje w pętli, a gdy zostanie wywołany z dowolnego miejsca, zawsze zwróci następną liczbę w sekwencji:
Teraz masz dwa obiekty generatora liczb Fibonacciego, które możesz wywoływać z dowolnego miejsca w kodzie i zawsze będą zwracać coraz większe liczby Fibonacciego w następujący sposób:
Zaletą generatorów jest to, że otaczają one stan bez konieczności przechodzenia przez obręcz tworzenia obiektów. Jednym ze sposobów myślenia o nich są „funkcje”, które zapamiętują ich stan wewnętrzny.
Mam przykład Fibonacciego z Python Generators - czym one są? a przy odrobinie wyobraźni możesz wymyślić wiele innych sytuacji, w których generatory stanowią doskonałą alternatywę dla
for
pętli i innych tradycyjnych konstrukcji iteracyjnych.źródło
Proste wyjaśnienie: rozważ
for
oświadczeniePrzez większość czasu wszystkie elementy
iterable
nie muszą być od samego początku, ale można je generować w locie, gdy są potrzebne. W obu przypadkach może to być znacznie bardziej wydajneInnym razem nie znasz nawet wszystkich przedmiotów przed czasem. Na przykład:
Nie masz możliwości wcześniejszego poznania wszystkich poleceń użytkownika, ale możesz skorzystać z takiej ładnej pętli, jeśli generator generuje polecenia:
Za pomocą generatorów można także wykonywać iteracje po nieskończonych sekwencjach, co oczywiście nie jest możliwe podczas iteracji po kontenerach.
źródło
itertool
- patrzcycles
.Moje ulubione zastosowania to operacje „filtrowania” i „zmniejszania”.
Powiedzmy, że czytamy plik i chcemy tylko linii zaczynających się od „##”.
Następnie możemy użyć funkcji generatora w odpowiedniej pętli
Przykład redukcji jest podobny. Załóżmy, że mamy plik, w którym musimy zlokalizować bloki
<Location>...</Location>
linii. [Nie tagi HTML, ale linie wyglądające jak tagi].Ponownie możemy użyć tego generatora we właściwej pętli for.
Chodzi o to, że funkcja generatora pozwala nam filtrować lub redukować sekwencję, tworząc kolejną sekwencję po jednej wartości na raz.
źródło
fileobj.readlines()
czytałby cały plik do listy w pamięci, co przeczy celowi używania generatorów. Ponieważ obiekty plików są już iterowalne, możeszfor b in your_generator(fileobject):
zamiast tego użyć . W ten sposób plik będzie odczytywany jeden wiersz na raz, aby uniknąć odczytu całego pliku.Praktycznym przykładem, w którym można skorzystać z generatora jest, jeśli masz jakiś kształt i chcesz iterować po jego rogach, krawędziach itp. Do własnego projektu ( tutaj kod źródłowy ) miałem prostokąt:
Teraz mogę utworzyć prostokąt i zapętlić jego rogi:
Zamiast tego
__iter__
możesz mieć metodęiter_corners
i wywołać ją za pomocąfor corner in myrect.iter_corners()
. Jest po prostu bardziej elegancki w użyciu,__iter__
ponieważ odtąd możemy używać nazwy instancji klasy bezpośrednio wfor
wyrażeniu.źródło
Zasadniczo unika się funkcji oddzwaniania podczas iteracji nad utrzymaniem stanu wejścia.
Zobacz tutaj i tutaj, aby zobaczyć, co można zrobić za pomocą generatorów.
źródło
Kilka dobrych odpowiedzi tutaj, ale polecam również pełną lekturę samouczka programowania funkcjonalnego Python, który pomaga wyjaśnić niektóre z bardziej potencjalnych przypadków użycia generatorów.
źródło
Ponieważ nie wspomniano o metodzie wysyłania generatora, oto przykład:
Pokazuje możliwość wysłania wartości do działającego generatora. Bardziej zaawansowany kurs na temat generatorów w poniższym filmie (w tym
yield
z eksploracji, generatorów do przetwarzania równoległego, przekraczania limitu rekurencji itp.)David Beazley o generatorach na PyCon 2014
źródło
Używam generatorów, gdy nasz serwer internetowy działa jako serwer proxy:
źródło
Stosy rzeczy. Za każdym razem, gdy chcesz wygenerować sekwencję elementów, ale nie chcesz „zmaterializować” ich wszystkich na liście jednocześnie. Na przykład możesz mieć prosty generator, który zwraca liczby pierwsze:
Następnie możesz użyć tego do wygenerowania produktów kolejnych liczb pierwszych:
Są to dość trywialne przykłady, ale możesz zobaczyć, jak może być przydatne do przetwarzania dużych (potencjalnie nieskończonych!) Zestawów danych bez generowania ich z wyprzedzeniem, co jest tylko jednym z bardziej oczywistych zastosowań.
źródło
Nadaje się również do drukowania liczb pierwszych do n:
źródło