Za što možete koristiti Python generatorske funkcije?

Počinjem učiti Python, i naišao sam na generacijske funkcije koje u njima imaju instrukcije o prinosu. Želim znati koje su vrste problema stvarno učinkovite u izvršavanju tih funkcija.

182
19 сент. quamrana set 19 sep . 2008-09-19 17:58 '08 u 17:58 2008-09-19 17:58
@ 16 odgovora

Generatori vam daju lijenu ocjenu. Koristite ih, ponavljajte ih, bilo eksplicitno uz pomoć "za" ili implicitno, prenoseći ih bilo kojoj funkciji ili konstruktu koji se ponavlja. Možda mislite da generatori vraćaju nekoliko stavki kao da vraćaju popis, ali umjesto da ih sve vrate odjednom, vraćaju ih redom, a funkcija generatora se zaustavlja dok se ne zatraži sljedeći element.

Generatori su dobri za izračunavanje velikog skupa rezultata (posebno izračuni pomoću samih ciklusa), pri čemu ne znate želite li sve rezultate ili gdje ne želite istovremeno dodijeliti memoriju za sve rezultate. Ili u situacijama kada generator koristi drugačiji generator ili troši neki drugi resurs, a pogodnije je da se to dogodi što je kasnije moguće.

Druga upotreba generatora (to je uistinu ista stvar) je zamjena povratnih poziva iteracijom. U nekim situacijama želite da funkcija obavi puno posla i ponekad ga prijavite pozivatelju. Obično se to radi pomoću funkcije povratnog poziva. Taj povratni poziv prosljeđujete radnoj funkciji i povremeno poziva taj povratni poziv. Pristup generatora je da radna funkcija (sada generator) ne zna ništa o povratnom pozivu i jednostavno daje kad god želi nešto reći. Pozivatelj, umjesto pisanja posebnog povratnog poziva i prenošenja svojih funkcija na posao, sva izvješća rade u maloj petlji "oko" generatora.

Na primjer, recimo da ste napisali program za pretraživanje datoteka. Možete pretražiti cijelu zbirku, prikupiti rezultate i prikazati ih jednu po jednu. Svi rezultati moraju biti prikupljeni prije nego što pokažete prvi, a svi rezultati će biti u memoriji u isto vrijeme. Ili možete prikazati rezultate dok ih ne pronađete, što će biti učinkovitije u smislu memorije i mnogo više prilagođene korisniku. Potonje se može provesti prosljeđivanjem funkcije ispisa u funkciju pretraživanja datoteka, ili se to može učiniti jednostavno tako da funkcija pretraživanja bude generator i ponavlja rezultat.

Ako želite vidjeti primjer zadnja dva pristupa, pogledajte os.path () (stari sustav datoteka s funkcijom povratnog poziva) i os.walk () (novi generator datotečnog sustava). Naravno, ako doista želite prikupiti sve rezultate na popisu, pristup generatora je trivijalan za prelazak na pristup s velikim popisom:

 big_list = list(the_generator) 
208
19 сент. Odgovor Thomasu Woutersu 19. rujna 2008-09-19 18:09 '08 u 18:09 2008-09-19 18:09

Jedan od razloga korištenja generatora je da odluka bude razumljivija za neke odluke.

Druga je obrada rezultata jedan po jedan, izbjegavajući stvaranje ogromnih popisa rezultata koje biste ionako obradili.

Ako imate funkciju prelazak-do-n:

 # function version def fibon(n): a = b = 1 result = [] for i in xrange(n): result.append(a) a, b = b, a + b return result 

Možete jednostavno napisati funkciju kao što je ova:

border=0
 # generator version def fibon(n): a = b = 1 for i in xrange(n): yield a a, b = b, a + b 

Funkcija je razumljivija. A ako koristite ovu funkciju:

 for x in fibon(1000000): print x, 

U ovom primjeru, ako koristite inačicu generatora, cijeli popis od 1000000 elemenata uopće neće biti kreiran, samo jedna vrijednost odjednom. To se neće dogoditi prilikom korištenja verzije popisa u kojoj će se najprije stvoriti popis.

83
19 сент. Odgovor se daje nosklo 19 sep . 2008-09-19 18:09 '08 u 18:09 2008-09-19 18:09

Vidi “Motivacija” u PEP 255 .

Ne-očita upotreba generatora stvara diskontinuirane funkcije koje vam omogućuju izvođenje radnji kao što su ažuriranje sučelja ili obavljanje više zadataka "istovremeno" (naizmjenično) bez korištenja niti.

36
19 сент. Odgovor je dan Nickolay 19 sep. 2008-09-19 18:07 '08 u 18:07 2008-09-19 18:07

Pronašla sam ovo objašnjenje koje čisti moje sumnje. Jer postoji mogućnost da osoba koja ne poznaje Generators , također ne zna o yield

povratak

Povratni izraz je mjesto gdje su sve lokalne varijable uništene, a dobivena vrijednost se vraća (vraća) pozivatelju. Ako se nakon nekog vremena pozove ista funkcija, funkcija će primiti novi skup varijabli.

izlaz

Ali što ako se lokalne varijable ne odbace kada izađemo iz funkcije? To znači da možemo resume the function gdje smo stali. Ovdje se uvodi koncept Generators i nastavlja se yield o yield kada se function zaustavi.

  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() 0 In [4]: gen.next() 1 In [5]: gen.next() 

Dakle, razlika između povratka i prinosa u Pythonu.

Izlazni izraz je funkcija koja obavlja funkciju generatora.

Dakle, generatori su jednostavan i moćan alat za stvaranje iteratora. Oni se pišu kao normalne funkcije, ali koriste operatore prinosa kada žele vratiti podatke. Svaki put kada se pozove sljedeći (), generator se nastavlja tamo gdje je napustio (pamti sve vrijednosti podataka i koji je izraz izvršen zadnji put).

29
18 янв. odgovor daje korisnik2134226 18 jan. 2013-01-18 11:17 '13 u 11:17 2013-01-18 11:17

Puferski. Kada je učinkovito primati podatke u velikim komadima, ali ih obraditi u malim fragmentima, generator može pomoći:

 def bufferedFetch(): while True: buffer = getBigChunkOfData() # insert some code to break on 'end of data' for i in buffer: yield i 

Gore navedeno omogućuje jednostavno odvajanje puferiranja od obrade. Funkcija potrošača sada može jednostavno dobiti vrijednosti jednu po jednu bez brige o puferiranju.

26
19 сент. Odgovor je dao Rafał Dowgird 19. rujna 2008-09-19 18:14 '08 u 18:14 2008-09-19 18:14

Primjer u stvarnom svijetu

Pretpostavimo da imate 100 milijuna domena u MySQL tablici i želite ažurirati alexa za svaku domenu.

Prije svega, morate odabrati imena domena iz baze podataka.

Pretpostavimo naziv tablice domains i naziv stupca domain

Ako koristite SELECT domain FROM domains , vratit će se 100 milijuna redaka koji će zauzeti puno memorije. Na taj se način vaš poslužitelj može srušiti

Dakle, odlučili ste pokrenuti program u serijama. Recimo da je veličina naše serije 1000.

U našem prvom obroku, upitat ćemo prvih 1000 redaka, provjeriti Alexa rang za svaku domenu i ažurirati redak baze podataka.

U našem drugom obroku radit ćemo na sljedećih 1000 linija. U našem trećem obroku bit će od 2001. do 3000, itd.

Sada nam je potrebna funkcija generatora koja generira naše serije.

Ovdje je naša funkcija generatora

 def ResultGenerator(cursor, batchsize=1000): while True: results = cursor.fetchmany(batchsize) if not results: break for result in results: yield result 

Kao što možete vidjeti, naša funkcija štedi rezultate. Ako ste koristili return ključnu riječ umjesto yield , cijela će funkcija biti dovršena kada dostigne povrat

 return - returns only once yield - returns multiple times 

Ako funkcija koristi ključnu riječ yield , tada njen generator.

Sada možete proći

 db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains") cursor = db.cursor() cursor.execute("SELECT domain FROM domains") for result in ResultGenerator(cursor): doSomethingWith(result) db.close() 
25
08 мая '14 в 2:20 2014-05-08 02:20 Odgovor dao Giri 8. svibnja 2009. u 2:20 2014-05-08 02:20

Otkrio sam da su generatori vrlo korisni za čišćenje vašeg koda i pružanje jedinstvenog načina za enkapsuliranje i moduliranje koda. U situaciji u kojoj morate stalno ispljunuti vrijednosti temeljene na vlastitoj internoj obradi, a kada trebate nazvati nešto s bilo kojeg mjesta u kodu (a ne samo unutar petlje ili bloka, na primjer), generatori su funkcija koja se koristi.

Apstraktan primjer je generator Fibonaccijevog broja koji ne živi u petlji i kada se pozove s bilo kojeg mjesta, uvijek će vratiti sljedeći broj u nizu:

 def fib(): first=0 second=1 yield first yield second while 1: next=first+second yield next first=second second=next fibgen1=fib() fibgen2=fib() 

Sada imate dva objekta generatora Fibonaccijevog broja koje možete pozvati s bilo kojeg mjesta u svom kodu i oni će uvijek uzastopno vraćati sve veći broj Fibonaccijevih brojeva:

 >>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next() 0 1 1 2 >>> fibgen2.next(); fibgen2.next() 0 1 >>> fibgen1.next(); fibgen1.next() 3 5 

Lijepa stvar kod generatora je to da one enkapsuliraju stanje bez potrebe da prolaze kroz obruče da bi stvorili objekte. Jedan od načina razmišljanja o njima su "funkcije" koje pamte njihovo unutarnje stanje.

Dobio sam primjer Fibonacci-a iz http://www.neotitans.com/resources/python/python-generators-tutorial.html i uz malo mašte možete pronaći mnoge druge situacije u kojima generatori predstavljaju odličnu alternativu za petlju i druge tradicionalne konstrukcije. ,

20
11 апр. Odgovor je dao Andz 11. travnja. 2009-04-11 23:55 '09 u 23:55 2009-04-11 23:55

Jednostavno objašnjenje: Razmislite for izjavu

 for item in iterable: do_stuff() 

U većini slučajeva, svi elementi u iterable ne moraju biti od početka, ali mogu biti generirani u letu, kao što su potrebni. To može biti mnogo učinkovitije kao u

  • (nikada ne morate istovremeno spremati sve elemente) i
  • (iteracija se može dovršiti prije nego što su potrebni svi elementi).

U drugim slučajevima, ne znate sve stavke prije vremena. Na primjer:

 for command in user_input(): do_stuff_with(command) 

Nemate mogućnost unaprijed znati sve korisničke naredbe, ali možete koristiti dobru petlju kao što je ova ako imate generator koji vam šalje naredbe:

 def user_input(): while True: wait_for_command() cmd = get_command() yield cmd 

Kod generatora, također možete imati iteraciju preko beskonačnih sekvenci, što, naravno, nije moguće kada se ponavlja kroz spremnike.

18
Odgovor je dF. 19. rujna 2008-09-19 18:15 '08 u 18:15 2008-09-19 18:15

Moje omiljene upotrebe su operacije "filtar" i "smanjenje".

Recimo da smo pročitali datoteku i da želite samo retke koje počinju s "##".

 def filter2sharps( aSequence ): for l in aSequence: if l.startswith("##"): yield l 

Tada možemo koristiti funkciju generatora u našoj vlastitoj petlji

 source= file( ... ) for line in filter2sharps( source.readlines() ): print line source.close() 

Primjer kratice je sličan. Recimo da imamo datoteku gdje moramo pronaći blokove <Location>...</Location> žice. [Ne HTML oznake, nego linije koje izgledaju kao oznake.]

 def reduceLocation( aSequence ): keep= False block= None for line in aSequence: if line.startswith("</Location"): block.append( line ) yield block block= None keep= False elif line.startsWith("<Location"): block= [ line ] keep= True elif keep: block.append( line ) else: pass if block is not None: yield block # A partial block, icky 

Opet, možemo koristiti ovaj generator u ispravnom za petlju.

 source = file( ... ) for b in reduceLocation( source.readlines() ): print b source.close() 

Ideja je da nam funkcija generatora omogućuje da filtriramo ili smanjimo slijed, stvarajući sljedeću sekvencu jednu vrijednost u isto vrijeme.

12
19 сент. odgovor dao S.Lott 19. rujna 2008-09-19 18:13 '08 u 18:13 2008-09-19 18:13

Praktičan primjer gdje možete koristiti generator je ako imate neki oblik, a želite dodirnuti njegove kutove, rubove ili nešto drugo. Za moj vlastiti projekt (izvorni kod ovdje ) imao sam pravokutnik:

 class Rect(): def __init__(self, x, y, width, height): self.l_top = (x, y) self.r_top = (x+width, y) self.r_bot = (x+width, y+height) self.l_bot = (x, y+height) def __iter__(self): yield self.l_top yield self.r_top yield self.r_bot yield self.l_bot 

Sada na njegovim uglovima mogu stvoriti pravokutnik i petlju:

 myrect=Rect(50, 50, 100, 100) for corner in myrect: print(corner) 

Umjesto __iter__ , možete imati iter_corners metodu i nazvati je pomoću for corner in myrect.iter_corners() . Jednostavnije je koristiti __iter__ , budući da možemo koristiti ime instance klase izravno u izrazu.

8
27 сент. Odgovor daje Pithikos 27. rujna . 2014-09-27 15:40 '14 u 15:40 2014-09-27 15:40

Uglavnom se izbjegavaju povratni pozivi pri ponovnom izvršavanju stanja spremanja ulaza.

Pogledajte ovdje i ovdje za pregled onoga što se može izvesti pomoću generatora.

6
19 сент. odgovor je dan MvdD 19 sep . 2008-09-19 18:09 '08 u 18:09 2008-09-19 18:09

Neki dobri odgovori su ovdje, međutim, ja bih također preporučiti potpuno čitanje Python Funkcionalni programski vodič , koji će vam pomoći objasniti neke od snažnije koristi generatora.

4
16 марта '15 в 17:17 2015-03-16 17:17 odgovor je dat shongololo 16. ožujka u 17:17 2015-03-16 17:17

Koristim generatore kada naš web-poslužitelj djeluje kao proxy:

  • Klijent zahtjeva od poslužitelja proxy poslužitelj
  • Poslužitelj počinje preuzimati ciljani URL
  • Poslužitelj vam omogućuje vraćanje rezultata klijentu čim ih primi.
2
19 сент. Odgovor je dao Brian 19. rujna. 2008-09-19 18:17 '08 u 18:17 2008-09-19 18:17

Budući da ovdje nije spomenuta metoda otpreme generatora, ovo je primjer:

 def test(): for i in xrange(5): val = yield print(val) t = test() # proceed to yield statement next(t) # send value to yield t.send(1) t.send('2') t.send([3]) 

Pokazuje mogućnost slanja vrijednosti pokrenutom generatoru. Napredniji tečaj generatora u videozapisu u nastavku (uključujući profitabilnost od istraživanja, generatore za paralelnu obradu, prevladavanje rekurzivnog ograničenja itd.)

David Baisley o generatorima u PyCon 2014

2
28 апр. Odgovor dao je John Damen 28. travnja 2014-04-28 10:21 '14 u 10:21 AM 2014-04-28 10:21

Gomile stvari. Svaki put kada želite stvoriti niz elemenata, ali ne želite da ih sve "materijaliziraju" na popis odjednom. Na primjer, možete imati jednostavan generator koji vraća prave brojeve:

 def primes(): primes_found = set() primes_found.add(2) yield 2 for i in itertools.count(1): candidate = i * 2 + 1 if not all(candidate % prime for prime in primes_found): primes_found.add(candidate) yield candidate 

Tada ga možete upotrijebiti za generiranje proizvoda sljedećih prostih brojeva:

 def prime_products(): primeiter = primes() prev = primeiter.next() for prime in primeiter: yield prime * prev prev = prime 

To su prilično trivijalni primjeri, ali možete vidjeti kako to može biti korisno za obradu velikih (potencijalno beskonačnih!) Skupova podataka bez prethodnog generiranja, što je samo jedna od najočitijih primjena.

1
19 сент. Odgovor je dao John Johnson 19. rujna. 2008-09-19 18:14 '08 u 18:14 2008-09-19 18:14

Također je korisno ispisati prave brojeve do n:

 def genprime(n=10): for num in range(3, n+1): for factor in range(2, num): if num%factor == 0: break else: yield(num) for prime_num in genprime(100): print(prime_num) 
0
22 сент. Odgovor je dat triptofan 22. rujna 2017-09-22 17:06 '17 u 17:06 2017-09-22 17:06

Ostala pitanja o oznakama ili Postavi pitanje