I denne vejledning lærer du, hvordan du nemt opretter iterationer ved hjælp af Python-generatorer, hvordan det adskiller sig fra iteratorer og normale funktioner, og hvorfor du skal bruge det.
Video: Python Generators
Generatorer i Python
Der er meget arbejde med at opbygge en iterator i Python. Vi er nødt til at implementere en klasse med __iter__()
og __next__()
metode, holde styr på interne tilstande og hæve, StopIteration
når der ikke er nogen værdier, der skal returneres.
Dette er både langvarigt og kontraintuitivt. Generator kommer til undsætning i sådanne situationer.
Python-generatorer er en enkel måde at oprette iteratorer på. Alt det arbejde, vi nævnte ovenfor, håndteres automatisk af generatorer i Python.
Kort sagt er en generator en funktion, der returnerer et objekt (iterator), som vi kan gentage (en værdi ad gangen).
Opret generatorer i Python
Det er ret simpelt at oprette en generator i Python. Det er lige så let som at definere en normal funktion, men med en yield
erklæring i stedet for en return
erklæring.
Hvis en funktion indeholder mindst en yield
sætning (den kan indeholde andre yield
eller return
udsagn), bliver den en generatorfunktion. Begge yield
og return
returnerer en værdi fra en funktion.
Forskellen er, at mens en return
erklæring afslutter en funktion fuldstændigt, yield
sætter udsagnet funktionen på pause, og gemmer alle dens tilstande og fortsætter senere derfra ved successive opkald.
Forskelle mellem generatorfunktion og normal funktion
Her er hvordan en generatorfunktion adskiller sig fra en normal funktion.
- Generatorfunktionen indeholder en eller flere
yield
udsagn. - Når det kaldes, returnerer det et objekt (iterator) men starter ikke udførelsen med det samme.
- Metoder som
__iter__()
og__next__()
implementeres automatisk. Så vi kan gentage genstandene ved hjælp afnext()
. - Når funktionen først har givet, stoppes funktionen, og kontrollen overføres til den, der ringer op.
- Lokale variabler og deres tilstande huskes mellem på hinanden følgende opkald.
- Endelig
StopIteration
hæves automatisk , når funktionen ophører, ved yderligere opkald.
Her er et eksempel for at illustrere alle ovenstående punkter. Vi har en generatorfunktion navngivet my_gen()
med flere yield
udsagn.
# 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
En interaktiv kørsel i tolken er angivet nedenfor. Kør disse i Python-skalen for at se output.
>>> # 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 at bemærke i eksemplet ovenfor er, at værdien af variablen n huskes mellem hvert opkald.
I modsætning til normale funktioner ødelægges de lokale variabler ikke, når funktionen giver. Desuden kan generatorobjektet kun gentages en gang.
For at genstarte processen skal vi oprette et andet generatorobjekt ved hjælp af noget lignende a = my_gen()
.
En sidste ting at bemærke er, at vi kan bruge generatorer med til sløjfer direkte.
Dette skyldes, at en for
sløjfe tager en iterator og gentager den ved hjælp af next()
funktionen. Den slutter automatisk, når den StopIteration
hæves. Tjek her for at vide, 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 kører programmet, vil output være:
Dette udskrives først 1 Dette udskrives andet 2 Dette udskrives sidst 3
Python-generatorer med en løkke
Ovenstående eksempel har mindre brug, og vi studerede det bare for at få en idé om, hvad der skete i baggrunden.
Normalt implementeres generatorfunktioner med en sløjfe, der har en passende afslutningstilstand.
Lad os tage et eksempel på en generator, der vender 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)
Produktion
olleh
I dette eksempel har vi brugt range()
funktionen til at få indekset i omvendt rækkefølge ved hjælp af for-sløjfen.
Bemærk : Denne generatorfunktion fungerer ikke kun med strenge, men også med andre slags iterables som liste, tuple osv.
Python Generator Expression
Enkle generatorer kan let oprettes med det samme ved hjælp af generatorudtryk. Det gør det nemt at bygge generatorer.
Svarende til lambda-funktionerne, der skaber anonyme funktioner, skaber generatorudtryk anonyme generatorfunktioner.
Syntaksen for generatorekspression svarer til en listeforståelse i Python. Men de firkantede parenteser erstattes med runde parenteser.
Den største forskel mellem en listeforståelse og et generatorudtryk er, at en listeforståelse producerer hele listen, mens generatorudtrykket producerer et element ad gangen.
They have lazy execution ( producing items only when asked for ). For this reason, a generator expression is much more memory efficient than an equivalent list comprehension.
# 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)
Output
(1, 9, 36, 100)
We can see above that the generator expression did not produce the required result immediately. Instead, it returned a generator object, which produces items only on demand.
Here is how we can start getting items from the generator:
# 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)
When we run the above program, we get the following output:
1 9 36 100 Traceback (most recent call last): File "", line 15, in StopIteration
Generator expressions can be used as function arguments. When used in such a way, the round parentheses can be dropped.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Use of Python Generators
There are several reasons that make generators a powerful implementation.
1. Easy to Implement
Generators can be implemented in a clear and concise way as compared to their iterator class counterpart. Following is an example to implement a sequence of power of 2 using an iterator class.
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
The above program was lengthy and confusing. Now, let's do the same using a generator function.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Since generators keep track of details automatically, the implementation was concise and much cleaner.
2. Memory Efficient
A normal function to return a sequence will create the entire sequence in memory before returning the result. This is an overkill, if the number of items in the sequence is very large.
Generator implementation of such sequences is memory friendly and is preferred since it only produces one item at a time.
3. Represent Infinite Stream
Generatorer er fremragende medier, der repræsenterer en uendelig strøm af data. Uendelige streams kan ikke lagres i hukommelsen, og da generatorer kun producerer et element ad gangen, kan de repræsentere en uendelig datastrøm.
Følgende generatorfunktion kan generere alle lige tal (i det mindste i teorien).
def all_even(): n = 0 while True: yield n n += 2
4. Rørledningsgeneratorer
Flere generatorer kan bruges til at pipeline en række operationer. Dette illustreres bedst ved hjælp af et eksempel.
Antag, at vi har en generator, der producerer tallene i Fibonacci-serien. Og vi har en anden generator til kvadrering af tal.
Hvis vi vil finde ud af summen af kvadrater af tal i Fibonacci-serien, kan vi gøre det på følgende måde ved at pipelere output fra generatorfunktioner 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))))
Produktion
4895
Denne rørledning er effektiv og let at læse (og ja, meget køligere!).