Razumijevanje generatora u Pythonu

Trenutno čitam Python kuhanje i sada gledam generatore. Teško mi je izvrnuti glavu.

Kako nastavljam iz pozadine Java-a, postoji li ekvivalent Java-a? Knjiga je govorila o "Producentu / Potrošaču", međutim, kad čujem što mislim o rezanju.

Što je generator i zašto ga koristite? Bez navođenja bilo koje knjige, očito (osim ako ne pronađete pristojan, pojednostavljen odgovor izravno iz knjige). Možda s primjerima, ako se osjećate velikodušno!

171
18 нояб. Federer je postavljen 18. studenog. 2009-11-18 16:46 '09 u 4:46 2009-11-18 16:46
@ 11 odgovora

Napomena: Ovaj post preuzima sintaksu Python 3.x. bodež;

Generator je jednostavno funkcija koja vraća objekt na koji možete nazvati next , tako da za svaki poziv vraća neku vrijednost dok ne StopIteration iznimku StopIteration , StopIteration da su sve vrijednosti generirane. Takav se objekt naziva iterator.

Normalne funkcije vraćaju jednu vrijednost pomoću return , kao u Javi. U Pythonu, međutim, postoji alternativa koja se zove yield . Korištenje yield bilo gdje u funkciji čini ga generatorom. Poštujte ovaj kod:

 >>> def myGen(n): ... yield n ... yield n + 1 ... >>> g = myGen(6) >>> next(g) 6 >>> next(g) 7 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 

Kao što možete vidjeti, myGen(n) je funkcija koja daje n i n + 1 . Svaki poziv do next daje jednu vrijednost sve dok se ne primi sve vrijednosti. for završava next poziv u pozadini, dakle:

 >>> for n in myGen(6): ... print(n) ... 6 7 

Slično tome, postoje izrazi generatora koji pružaju sredstva za kratak opis nekih uobičajenih tipova generatora:

 >>> g = (n for n in range(3, 5)) >>> next(g) 3 >>> next(g) 4 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 

Napominjemo da su izrazi generatora vrlo slični popisu koncepata :

 >>> lc = [n for n in range(3, 5)] >>> lc [3, 4] 

Imajte na umu da je objekt generatora generiran jednom, ali se njegov kod ne pokreće u isto vrijeme. Samo pozivi next zapravo izvršavaju (dio) koda. Izvršenje koda u generatoru zaustavlja se nakon yield o yield , nakon čega vraća vrijednost. Sljedeći sljedeći poziv prisiljava izvršenje da se nastavi u stanju u kojem generator ostaje nakon zadnjeg yield . To je temeljna razlika u odnosu na regularne funkcije: uvijek se pokreću na "vrhu" i ispuštaju svoje stanje nakon povratne vrijednosti.

Postoji mnogo stvari koje treba reći o ovoj temi. Na primjer, moguće je vratiti podatke za send generatoru ( referenci ). Ali predlažem vam da ne gledate dok ne shvatite osnovni koncept generatora.

Sada možete pitati: zašto koristiti generatore? Postoji nekoliko dobrih razloga:

  • Neki koncepti mogu se ukratko opisati pomoću generatora.
  • Umjesto kreiranja funkcije koja vraća popis vrijednosti, možete napisati generator koji generira vrijednosti u hodu. To znači da popis ne bi trebao biti konstruiran, što znači da je dobiveni kod učinkovitiji u smislu memorije. Dakle, možete čak opisati tokove podataka koji bi jednostavno bili preveliki da bi se uklopili u memoriju.
  • Generatori vam omogućuju da prirodno opišete beskonačne tokove. Razmotrite, na primjer, Fibonaccijeve brojeve :

     >>> def fib(): ... a, b = 0, 1 ... while True: ... yield a ... a, b = b, a + b ... >>> import itertools >>> list(itertools.islice(fib(), 10)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 

    Ovaj kod koristi itertools.islice za uzimanje konačnog broja elemenata iz beskonačnog toka. Potičemo vas da se upoznate s značajkama itertools modula, jer su oni važni alati za pisanje složenih generatora s velikom lakoćom.


bodež; O Pythonu <= 2.6: u gornjim primjerima, next je funkcija koja poziva metodu __next__ za dati objekt. U Pythonu <= 2.6 koristi se nešto drugačija metoda, odnosno o.next() umjesto next(o) . Python 2.7 ima next() poziv na .next , tako da ne morate koristiti sljedeće u 2.7:

 >>> g = (n for n in range(3, 5)) >>> g.next() 3 
320
18 нояб. Odgovor je dat Stephan202 18 Nov. 2009-11-18 16:54 '09 u 16:54 2009-11-18 16:54

Generator je zapravo funkcija koja vraća (podaci) prije nego što završi, ali se zaustavlja na toj točki i možete nastaviti funkciju u toj točki.

 >>> def myGenerator(): ... yield 'These' ... yield 'words' ... yield 'come' ... yield 'one' ... yield 'at' ... yield 'a' ... yield 'time' >>> myGeneratorInstance = myGenerator() >>> next(myGeneratorInstance) These >>> next(myGeneratorInstance) words 

i tako dalje Prednost generatora je u tome što budući da obrađuju podatke po jedan komad odjednom, možete obraditi velike količine podataka; Uz popise, prekomjerni memorijski zahtjevi mogu biti problem. Generatori, poput popisa, mogu se ponoviti, pa se mogu koristiti na isti način:

 >>> for word in myGeneratorInstance: ... print word These words come one at a time 

Imajte na umu da generatori, na primjer, pružaju drugi način rješavanja beskonačnosti

 >>> from time import gmtime, strftime >>> def myGen(): ... while True: ... yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) >>> myGeneratorInstance = myGen() >>> next(myGeneratorInstance) Thu, 28 Jun 2001 14:17:15 +0000 >>> next(myGeneratorInstance) Thu, 28 Jun 2001 14:18:02 +0000 

Generator enkapsulira beskonačnu petlju, ali to nije problem, jer svaki odgovor dobivate samo svaki put kada ga zatražite.

41
18 нояб. Odgovor daje Caleb Hattingh 18 nov. 2009-11-18 17:24 '09 u 17:24 2009-11-18 17:24

Prije svega, pojam generator bio je u početku pomalo loše definiran u Pythonu, što je dovelo do velike konfuzije. Vjerojatno mislite na iteratore i iteracije (pogledajte ovdje ). Zatim u Pythonu postoje i generacijske funkcije (koje vraćaju objekt generatora), generatorske objekte (koji su iteratori) i izraze generatora (koje vrednuje objekt generator).

Prema riječima pojmovnika za generator, čini se da je službena terminologija sada da je generator kratica za "funkciju generatora". U prošlosti su dokumenti definirali pojmove nedosljedno, ali na sreću, to je ispravljeno.

Bilo bi dobro da budete precizni i izbjegavajte pojam "generator" bez dodatnih specifikacija.

23
18 нояб. odgovor je dat nikow 18 nov. 2009-11-18 17:35 '09 u 17:35 2009-11-18 17:35

Generatori se mogu smatrati kratkim za stvaranje iteratora. Ponašaju se kao Java iterator. primjer:

 >>> g = (x for x in range(10)) >>> g <generator object <genexpr> at 0x7fac1c1e6aa0> >>> g.next() 0 >>> g.next() 1 >>> g.next() 2 >>> list(g) # force iterating the rest [3, 4, 5, 6, 7, 8, 9] >>> g.next() # iterator is at the end; calling next again will throw Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 

Nadam se da ovo pomaže / to je ono što tražite.

Update:

Kao i mnogi drugi odgovori, postoje različiti načini za stvaranje generatora. Možete koristiti sintaksu zagrada, kao u mom gornjem primjeru, ili možete koristiti prinos. Još jedna zanimljiva značajka je da generatori mogu biti "beskonačni" - iteratori koji se ne zaustavljaju:

 >>> def infinite_gen(): ... n = 0 ... while True: ... yield n ... n = n + 1 ... >>> g = infinite_gen() >>> g.next() 0 >>> g.next() 1 >>> g.next() 2 >>> g.next() 3 ... 
21
18 нояб. odgovor je prešutio 18. studenoga 2009-11-18 16:53 '09 u 16:53 2009-11-18 16:53

Nema ekvivalenta jave.

Evo malo izmišljenog primjera:

 #! /usr/bin/python def mygen(n): x = 0 while x < n: x = x + 1 if x % 3 == 0: yield x for a in mygen(100): print a 

Postoji ciklus u generatoru koji radi od 0 do n, a ako je varijabla ciklusa višestruka od 3, daje varijablu.

Tijekom svake iteracije for petlje, izvršava se generator. Ako je ovo prvi početak generatora, on počinje od početka, inače se nastavlja iz prethodnog vremena.

9
18 нояб. Odgovor daje Wernsey 18. studenog. 2009-11-18 16:58 '09 u 16:58 2009-11-18 16:58

Volim opisivati ​​generatore koji imaju pristojnu pozadinu u programskim jezicima i računalstvu, u smislu okvira okvira.

U mnogim jezicima postoji stog, na vrhu kojeg je trenutni "okvir" stog. Okvir stog uključuje prostor dodijeljen za varijable lokalne funkciji, uključujući argumente koji su proslijeđeni ovoj funkciji.

Kada pozovete funkciju, trenutna točka izvršenja ("program counter" ili "ekvivalent") je gurnuta u stog i kreira se novi stog. Tada se izvršenje prenosi na početak pozvane funkcije.

S regularnim funkcijama, u nekom trenutku, funkcija vraća vrijednost, a stog "iskače". Okvir funkcije stog se odbacuje, a izvršenje se nastavlja na prethodnom mjestu.

Kada je funkcija generator, ona može vratiti vrijednost bez ispuštanja okvira stog pomoću naredbe yield. Spremljene su vrijednosti lokalnih varijabli i brojača programa unutar funkcije. To omogućuje da se generator nastavi kasnije, uz nastavak izvršavanja iz instrukcije o prinosu, i može izvršiti više koda i vratiti drugu vrijednost.

Prije Pythona 2.5 sve su to bile generatori. Python 2.5 je dodao mogućnost prijenosa vrijednosti na generator. Istovremeno, prenesena vrijednost je dostupna kao izraz dobiven kao rezultat instrukcije prinosa, koja je privremeno vratila kontrolu (i vrijednost) iz generatora.

Ključna prednost generatora je u tome što je "stanje" funkcije očuvano, za razliku od uobičajenih funkcija, kada svaki put kad se stackov okvir ispusti, gubite sve ovo "stanje". Druga prednost je da su neki od poziva funkcije funkcije (stvaranje i brisanje okvira stogova) eliminirani, iako je to obično mala prednost.

7
19 дек. Odgovor dao Peter Hansen 19. prosinca. 2009-12-19 13:50 '09 u 13:50 2009-12-19 13:50

Jedina stvar koju mogu dodati Stephan202-ovom odgovoru je preporuka da pogledate David Beazley PyCon '08 Presentation Generating Tricks za programatore sustava, što je najbolje odvojeno objašnjenje kako i zašto se generatori mogu vidjeti negdje. To je ono što me odvelo iz "Pythona izgleda zabavno" na "Ovo je ono što sam tražio." Nalazi se na adresi http://www.dabeaz.com/generators/ .

6
18 нояб. Odgovor dao Robert Rossney 18. studenog 2009-11-18 20:54 '09 u 20:54 2009-11-18 20:54

To pomaže da se napravi jasna razlika između funkcije foo i foo (n) generatora:

 def foo(n): yield n yield n+1 

foo je funkcija. foo (6) je objekt generatora.

Tipičan način korištenja objekta generatora je u petlji:

 for n in foo(6): print(n) 

Tiskanje u petljama

 # 6 # 7 

Zamislite generator kao obnovljivu značajku.

yield ponaša kao return u smislu da dobivene vrijednosti vraća generator. Za razliku od povratka, sljedeći put kada se vrijednost ponudi generatoru, funkcija generatora, foo, nastavlja se tamo gdje je zaustavljena - nakon posljednje izjave o pražnjenju - i nastavlja raditi dok ne naiđe na drugu operaciju. prinos.

Iza kulisa, kada pozovete bar=foo(6) panela s objektima generatora je definirana da biste imali next atribut.

Možete ga sami pozvati da biste dobili vrijednosti dobivene iz foo:

 next(bar) # Works in Python 2.6 or Python 3.x bar.next() # Works in Python 2.5+, but is deprecated. Use next() if possible. 

Kada foo završi (i više nema vrijednosti), pozivanje next(bar) uzrokuje pogrešku StopInteration.

5
18 нояб. Odgovor se daje unutbu 18 nov. 2009-11-18 17:15 '09 u 17:15 2009-11-18 17:15

Ova će poruka koristiti Fibonaccijeve brojeve kao alat za objašnjenje korisnosti Python generatora .

Ova poruka će sadržavati i C ++ i Python kod.

Fibonaccijevi brojevi su definirani kao slijed: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....

Ili općenito:

 F0 = 0 F1 = 1 Fn = Fn-1 + Fn-2 

To se može lako prevesti u C ++ funkciju:

 size_t Fib(size_t n) { //Fib(0) = 0 if(n == 0) return 0; //Fib(1) = 1 if(n == 1) return 1; //Fib(N) = Fib(N-2) + Fib(N-1) return Fib(n-2) + Fib(n-1); } 

Ali ako želite ispisati prvih šest Fibonaccijevih brojeva, ponovno ćete izračunati skup vrijednosti koristeći gore navedenu funkciju.

Na primjer: Fib(3) = Fib(2) + Fib(1) , ali Fib(2) također ponovno broji Fib(1) . Što je viša vrijednost koju želite izračunati, to će biti gore.

Stoga bi moglo biti primamljivo ponovno napisati gore navedeno, a main pratiti stanje.

 // Not supported for the first two elements of Fib size_t GetNextFib(size_t  size_t  { int result = pp + p; pp = p; p = result; return result; } int main(int argc, char *argv[]) { size_t pp = 0; size_t p = 1; std::cout << "0 " << "1 "; for(size_t i = 0; i <= 4; ++i) { size_t fibI = GetNextFib(pp, p); std::cout << fibI << " "; } return 0; } 

Ali to je vrlo ružno, i to u velikoj mjeri komplicira našu logiku. Bilo bi bolje ne brinuti se o državi u našoj main funkciji.

Mogli bismo vratiti vrijednosti vector i koristiti iterator za iteraciju nad ovim skupom vrijednosti, ali to zahtijeva puno memorije odjednom za veliki broj vraćenih vrijednosti.

Dakle, natrag na naš stari pristup, što će se dogoditi ako želimo učiniti nešto drugo osim ispisa brojeva? Morat ćemo kopirati i zalijepiti cijeli blok koda u main i promijeniti izlazne izraze na sve što smo htjeli učiniti. Ako kopirate i zalijepite kôd, trebali biste ga ukloniti. Ne želiš biti upucan, zar ne?

Da bismo riješili te probleme i kako bismo izbjegli pucanje, možemo blokirati ovaj blok koda pomoću funkcije povratnog poziva. Svaki put kad se naiđe novi Fibonaccijev broj, zovemo funkciju povratnog poziva.

 void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t)) { if(max-- == 0) return; FoundNewFibCallback(0); if(max-- == 0) return; FoundNewFibCallback(1); size_t pp = 0; size_t p = 1; for(;;) { if(max-- == 0) return; int result = pp + p; pp = p; p = result; FoundNewFibCallback(result); } } void foundNewFib(size_t fibI) { std::cout << fibI << " "; } int main(int argc, char *argv[]) { GetFibNumbers(6, foundNewFib); return 0; } 

Ovo je očigledno poboljšanje, vaša logika u main nije toliko pretrpana, i možete raditi što god želite s Fibonaccijevim brojevima, samo definirati nove povratne pozive.

Ali to još nije savršeno. Što ako samo želite dobiti prva dva Fibonaccijeva broja, a zatim učiniti nešto, a zatim dobiti još nekoliko, a onda učiniti nešto drugo?

Pa, mogli bismo nastaviti kao što smo bili, i mogli bismo ponovno početi dodavati stanje main , ostavljajući GetFibNumbers počevši od proizvoljne točke. Ali to će još više povećati naš kod, a već izgleda preveliko za jednostavan zadatak, na primjer, za ispis Fibonaccijevih brojeva.

Mogli bismo implementirati model proizvođača i potrošača kroz nekoliko niti. Ali još više komplicira kod.

Umjesto toga, govorimo o generatorima.

Python ima vrlo lijepu funkciju jezika koja rješava probleme poput ovih generatora.

Generator vam omogućuje da izvršite neku funkciju, zaustavite se na proizvoljnoj točki, a zatim nastavite ponovno kada prestanete. Vraća vrijednost svaki put.

Razmotrite sljedeći kod koji koristi generator:

 def fib(): pp, p = 0, 1 while 1: yield pp pp, p = p, pp+p g = fib() for i in range(6): g.next() 

Što nam daje rezultate:

0 1 1 2 3 5

yield koristi se zajedno s Python generatorima. Ona sprema stanje funkcije i vraća poskupljenu vrijednost. Sljedeći put kada pozovete sljedeću funkciju () na generatoru, ona će se nastaviti tamo gdje ostaje izlaz.

To je mnogo čišće od koda funkcije povratnog poziva. Imamo čišći kod, manji kôd i da ne spominjemo mnogo funkcionalniji kod (Python dopušta proizvoljno velike cjeline).

Izvor

4
19 дек. Odgovor je dao Brian R. Bondy 19. prosinca. 2009-12-19 08:58 '09 u 8:58 am 2009-12-19 08:58

Vjerujem da je prvo pojavljivanje iteratora i generatora bilo u programskom jeziku Icon, prije dvadesetak godina.

Možete uživati ​​u Pregledu ikona , koji vam omogućuje da omotate glavu oko sebe bez usredotočenja na sintaksu (budući da je Icon jezik koji vjerojatno ne znate, a Griswold je objasnio prednosti svog jezika ljudima koji su došli s drugih jezika).

Nakon čitanja nekoliko paragrafa, korisnost generatora i iteratora može postati očitija.

2
18 нояб. Odgovor daje Nosredna 18. studenog. 2009-11-18 17:53 '09 u 17:53 2009-11-18 17:53

Iskustvo korištenja popisa pokazuje njegovu široku rasprostranjenost tijekom Pythona. Međutim, u mnogim slučajevima uporabe nije potrebno imati kompletan popis stvoren u memoriji. Umjesto toga, potrebno je samo sortirati stavke jednu po jednu.

Na primjer, sljedeći kod zbrajanja će izgraditi potpuni popis kvadrata u memoriji, ponavljati te vrijednosti i, kada veza više nije potrebna, izbrišite popis:

sum([x*x for x in range(10)])

Memorija se pohranjuje pomoću izraza generatora:

sum(x*x for x in range(10))

Slične prednosti dodjeljuju se i dizajnerima kontejnerskih objekata:

 s = Set(word for line in page for word in line.split()) d = dict( (k, func(k)) for k in keylist) 

Izrazi generatora su posebno korisni za funkcije kao što su sum (), min () i max (), koje smanjuju ponovni unos na jednu vrijednost:

 max(len(line) for line in file if line.strip()) 

više

2
24 нояб. Saqib Mujtaba odgovor je 24. studenog. 2017-11-24 21:28 '17 u 21:28 2017-11-24 21:28

Ostala pitanja o oznakama ili Postavi pitanje