I denne opplæringen lærer du hvordan du enkelt kan lage iterasjoner ved hjelp av Python-generatorer, hvordan det er forskjellig fra iteratorer og normale funksjoner, og hvorfor du bør bruke det.
Video: Python Generators
Generatorer i Python
Det er mye arbeid med å bygge en iterator i Python. Vi må implementere en klasse med __iter__()
og __next__()
metode, holde styr på interne tilstander og heve StopIteration
når det ikke er noen verdier som skal returneres.
Dette er både langt og kontraintuitivt. Generator kommer til unnsetning i slike situasjoner.
Python-generatorer er en enkel måte å lage iteratorer på. Alt arbeidet vi nevnte ovenfor håndteres automatisk av generatorer i Python.
Enkelt sagt er en generator en funksjon som returnerer et objekt (iterator) som vi kan itere over (en verdi om gangen).
Lag generatorer i Python
Det er ganske enkelt å lage en generator i Python. Det er like enkelt som å definere en normal funksjon, men med en yield
uttalelse i stedet for en return
uttalelse.
Hvis en funksjon inneholder minst en yield
setning (den kan inneholde andre yield
eller return
setninger), blir den en generatorfunksjon. Både yield
og return
vil returnere noen verdi fra en funksjon.
Forskjellen er at mens en return
setning avslutter en funksjon helt, yield
setter setningen funksjonen på pause, og lagrer alle tilstandene og fortsetter senere derfra på påfølgende samtaler.
Forskjeller mellom generatorfunksjon og normalfunksjon
Slik skiller en generatorfunksjon seg fra en normal funksjon.
- Generatorfunksjonen inneholder en eller flere
yield
utsagn. - Når det kalles, returnerer det et objekt (iterator), men starter ikke kjøringen umiddelbart.
- Metoder som
__iter__()
og__next__()
implementeres automatisk. Så vi kan iterere gjennom elementene ved hjelp avnext()
. - Når funksjonen gir seg, blir funksjonen midlertidig stoppet og kontrollen overført til den som ringer.
- Lokale variabler og deres tilstander huskes mellom påfølgende samtaler.
- Til slutt, når funksjonen avsluttes,
StopIteration
heves automatisk ved ytterligere samtaler.
Her er et eksempel for å illustrere alle punktene som er oppgitt ovenfor. Vi har en generatorfunksjon my_gen()
med flere yield
utsagn.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
Et interaktivt løp i tolken er gitt nedenfor. Kjør disse i Python-skallet for å se utdataene.
>>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last):… StopIteration >>> next(a) Traceback (most recent call last):… StopIteration
En interessant ting å merke seg i eksemplet ovenfor er at verdien av variabelen n huskes mellom hver samtale.
I motsetning til normale funksjoner blir ikke de lokale variablene ødelagt når funksjonen gir. Videre kan generatorobjektet bare gjentas en gang.
For å starte prosessen på nytt må vi lage et annet generatorobjekt ved hjelp av noe lignende a = my_gen()
.
En siste ting å merke seg er at vi kan bruke generatorer med for løkker direkte.
Dette er fordi en for
sløyfe tar en iterator og itererer over den ved hjelp av next()
funksjonen. Den avsluttes automatisk når den StopIteration
heves. Sjekk her for å vite hvordan en for loop faktisk implementeres i Python.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item)
Når du kjører programmet, vil utdataene være:
Dette skrives ut først 1 Dette skrives ut andre 2 Dette skrives ut til slutt 3
Python Generators with a Loop
Eksemplet ovenfor har mindre bruk, og vi studerte det bare for å få en ide om hva som skjedde i bakgrunnen.
Normalt er generatorfunksjoner implementert med en sløyfe som har en passende avslutningstilstand.
La oss ta et eksempel på en generator som reverserer en streng.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str(i) # For loop to reverse the string for char in rev_str("hello"): print(char)
Produksjon
olleh
I dette eksemplet har vi brukt range()
funksjonen til å få indeksen i omvendt rekkefølge ved hjelp av for-sløyfen.
Merk : Denne generatorfunksjonen fungerer ikke bare med strenger, men også med andre typer iterables som liste, tupel, etc.
Python Generator Expression
Enkle generatorer kan enkelt opprettes ved å bruke generatoruttrykk. Det gjør det enkelt å bygge generatorer.
I likhet med lambda-funksjonene som skaper anonyme funksjoner, skaper generatoruttrykk anonyme generatorfunksjoner.
Syntaksen for generatoruttrykk er lik den for en listeforståelse i Python. Men de firkantede parentesene er erstattet med runde parenteser.
Den største forskjellen mellom en listeforståelse og et generatoruttrykk er at en listeforståelse produserer hele listen mens generatoruttrykket produserer ett element om gangen.
De har lat henrettelse (produserer bare varer når de blir bedt om det). Av denne grunn er et generatoruttrykk mye mer minneeffektivt enn en tilsvarende listeforståelse.
# Initialize the list my_list = (1, 3, 6, 10) # square each term using list comprehension list_ = (x**2 for x in my_list) # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x**2 for x in my_list) print(list_) print(generator)
Produksjon
(1, 9, 36, 100)
Vi kan se ovenfor at generatoruttrykket ikke ga det nødvendige resultatet umiddelbart. I stedet returnerte den et generatorobjekt som bare produserer varer etter behov.
Slik kan vi begynne å få ting fra generatoren:
# Initialize the list my_list = (1, 3, 6, 10) a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)
Når vi kjører programmet ovenfor, får vi følgende utdata:
1 9 36 100 Traceback (siste samtale sist): Fil "", linje 15, i StopIteration
Generatoruttrykk kan brukes som funksjonsargumenter. Når de brukes på en slik måte, kan de runde parentesene slippes.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Bruk av Python Generators
Det er flere grunner som gjør generatorer til en kraftig implementering.
1. Enkel å implementere
Generatorer kan implementeres på en klar og kortfattet måte sammenlignet med deres motpart i klassen iterator. Følgende er et eksempel på å implementere en sekvens av kraft på 2 ved hjelp av en iteratorklasse.
class PowTwo: def __init__(self, max=0): self.n = 0 self.max = max def __iter__(self): return self def __next__(self): if self.n> self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
Ovennevnte program var lang og forvirrende. La oss nå gjøre det samme ved hjelp av en generatorfunksjon.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Siden generatorer holder rede på detaljer automatisk, var implementeringen kortfattet og mye renere.
2. Minne effektivt
En normal funksjon for å returnere en sekvens vil opprette hele sekvensen i minnet før resultatet returneres. Dette er for mye, hvis antall elementer i sekvensen er veldig stort.
Generatorimplementering av slike sekvenser er minnevennlig og foretrekkes siden den bare produserer ett element om gangen.
3. Representer uendelig strøm
Generatorer er utmerkede medier for å representere en uendelig datastrøm. Uendelige strømmer kan ikke lagres i minnet, og siden generatorer produserer bare ett element om gangen, kan de representere en uendelig datastrøm.
Følgende generatorfunksjon kan generere alle partallene (i det minste teoretisk).
def all_even(): n = 0 while True: yield n n += 2
4. Rørledningsgeneratorer
Flere generatorer kan brukes til å pipeline en rekke operasjoner. Dette illustreres best ved hjelp av et eksempel.
Anta at vi har en generator som produserer tallene i Fibonacci-serien. Og vi har en annen generator for kvadrering av tall.
Hvis vi ønsker å finne ut summen av kvadrater av tall i Fibonacci-serien, kan vi gjøre det på følgende måte ved å rørledningen til utgangen fra generatorfunksjoner sammen.
def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num**2 print(sum(square(fibonacci_numbers(10))))
Produksjon
4895
Denne rørledningen er effektiv og lett å lese (og ja, mye kulere!).