Python-generatoren
Python-generatoren
In deze tutorial leer je hoe je eenvoudig iteraties kunt maken met Python-generators, hoe het verschilt van iterators en normale functies en waarom je het zou moeten gebruiken.
Video:Python-generatoren
Generatoren in Python
Er is veel werk aan het bouwen van een iterator in Python. We moeten een klasse implementeren met __iter__()
en __next__()
methode, houd interne toestanden bij en verhoog StopIteration
wanneer er geen waarden kunnen worden geretourneerd.
Dit is zowel langdradig als contra-intuïtief. Generator komt in dergelijke situaties te hulp.
Python-generatoren zijn een eenvoudige manier om iterators te maken. Al het werk dat we hierboven noemden wordt automatisch afgehandeld door generatoren in Python.
Simpel gezegd, een generator is een functie die een object (iterator) retourneert waarover we kunnen herhalen (één waarde per keer).
Generatoren maken in Python
Het is vrij eenvoudig om een generator te maken in Python. Het is net zo eenvoudig als het definiëren van een normale functie, maar met een yield
statement in plaats van een return
verklaring.
Als een functie ten minste één yield
. bevat statement (het kan andere yield
bevatten of return
statements), wordt het een generatorfunctie. Beide yield
en return
zal een waarde uit een functie teruggeven.
Het verschil is dat terwijl een return
statement beëindigt een functie volledig, yield
statement pauzeert de functie en bewaart alle statussen en gaat van daaruit verder bij opeenvolgende aanroepen.
Verschillen tussen generatorfunctie en normale functie
Hier is hoe een generatorfunctie verschilt van een normale functie.
- Generatorfunctie bevat een of meer
yield
verklaringen. - Wanneer het wordt aangeroepen, retourneert het een object (iterator) maar start de uitvoering niet onmiddellijk.
- Methoden zoals
__iter__()
en__next__()
worden automatisch uitgevoerd. Dus we kunnen de items herhalen met behulp vannext()
. - Zodra de functie resultaat geeft, wordt de functie gepauzeerd en wordt de controle overgedragen aan de beller.
- Lokale variabelen en hun status worden onthouden tussen opeenvolgende aanroepen.
- Ten slotte, wanneer de functie eindigt,
StopIteration
wordt automatisch verhoogd bij verdere oproepen.
Hier is een voorbeeld om alle bovengenoemde punten te illustreren. We hebben een generatorfunctie genaamd my_gen()
met meerdere yield
verklaringen.
# 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
Een interactieve run in de interpreter wordt hieronder gegeven. Voer deze uit in de Python-shell om de uitvoer te zien.
>>> # 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
Een interessant ding om op te merken in het bovenstaande voorbeeld is dat de waarde van variabele n wordt tussen elke oproep onthouden.
In tegenstelling tot normale functies worden de lokale variabelen niet vernietigd wanneer de functie meegeeft. Bovendien kan het generatorobject slechts één keer worden herhaald.
Om het proces opnieuw te starten, moeten we een ander generatorobject maken met iets als a = my_gen()
.
Een laatste ding om op te merken is dat we generatoren direct met for-lussen kunnen gebruiken.
Dit komt omdat een for
loop neemt een iterator en itereert erover met next()
functie. Het eindigt automatisch wanneer StopIteration
wordt verhoogd. Kijk hier om te weten hoe een for-lus in Python wordt geïmplementeerd.
# 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)
Wanneer u het programma uitvoert, is de uitvoer:
This is printed first 1 This is printed second 2 This is printed at last 3
Python-generatoren met een lus
Het bovenstaande voorbeeld is van minder nut en we hebben het bestudeerd om een idee te krijgen van wat er op de achtergrond gebeurde.
Normaal gesproken worden generatorfuncties geïmplementeerd met een lus met een geschikte afsluitconditie.
Laten we een voorbeeld nemen van een generator die een string omkeert.
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)
Uitvoer
o l l e h
In dit voorbeeld hebben we de range()
. gebruikt functie om de index in omgekeerde volgorde te krijgen met behulp van de for-lus.
Opmerking :Deze generatorfunctie werkt niet alleen met strings, maar ook met andere soorten iterables zoals list, tuple, etc.
Python Generator-expressie
Eenvoudige generatoren kunnen gemakkelijk on-the-fly worden gemaakt met behulp van generator-uitdrukkingen. Het maakt het bouwen van generatoren eenvoudig.
Net als de lambda-functies die anonieme functies creëren, creëren generator-expressies anonieme generatorfuncties.
De syntaxis voor generatorexpressie is vergelijkbaar met die van een lijstbegrip in Python. Maar de vierkante haken zijn vervangen door ronde haakjes.
Het belangrijkste verschil tussen een lijstbegrip en een generatoruitdrukking is dat een lijstbegrip de hele lijst produceert, terwijl de generatoruitdrukking één item tegelijk produceert.
Ze hebben een luie uitvoering (alleen items produceren wanneer daarom wordt gevraagd). Om deze reden is een generatoruitdrukking veel geheugenefficiënter dan een equivalent lijstbegrip.
# 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)
Uitvoer
[1, 9, 36, 100] <generator object <genexpr> at 0x7f5d4eb4bf50>
We kunnen hierboven zien dat de generatoruitdrukking niet onmiddellijk het vereiste resultaat opleverde. In plaats daarvan retourneerde het een generatorobject, dat alleen op verzoek items produceert.
Hier is hoe we items uit de generator kunnen halen:
# 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)
Wanneer we het bovenstaande programma uitvoeren, krijgen we de volgende uitvoer:
1 9 36 100 Traceback (most recent call last): File "<string>", line 15, in <module> StopIteration
Generator-expressies kunnen worden gebruikt als functieargumenten. Bij gebruik op een dergelijke manier kunnen de ronde haakjes worden verwijderd.
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100
Gebruik van Python-generatoren
Er zijn verschillende redenen die generatoren tot een krachtige implementatie maken.
1. Eenvoudig te implementeren
Generatoren kunnen op een duidelijke en beknopte manier worden geïmplementeerd in vergelijking met hun tegenhanger van de iteratorklasse. Hieronder volgt een voorbeeld om een reeks van macht van 2 te implementeren met behulp van een 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
Bovenstaand programma was lang en verwarrend. Laten we nu hetzelfde doen met een generatorfunctie.
def PowTwoGen(max=0):
n = 0
while n < max:
yield 2 ** n
n += 1
Omdat generatoren automatisch details bijhouden, was de implementatie beknopt en veel schoner.
2. Geheugen efficiënt
Een normale functie om een reeks terug te geven, zal de hele reeks in het geheugen maken voordat het resultaat wordt geretourneerd. Dit is een overkill als het aantal items in de reeks erg groot is.
Generator-implementatie van dergelijke reeksen is geheugenvriendelijk en heeft de voorkeur omdat het slechts één item tegelijk produceert.
3. Vertegenwoordig oneindige stroom
Generatoren zijn uitstekende media om een oneindige stroom gegevens weer te geven. Oneindige streams kunnen niet in het geheugen worden opgeslagen en aangezien generatoren slechts één item tegelijk produceren, kunnen ze een oneindige datastroom vertegenwoordigen.
De volgende generatorfunctie kan alle even getallen genereren (althans in theorie).
def all_even():
n = 0
while True:
yield n
n += 2
4. Generatoren voor pijpleidingen
Meerdere generatoren kunnen worden gebruikt om een reeks bewerkingen te pijplijnen. Dit wordt het best geïllustreerd aan de hand van een voorbeeld.
Stel dat we een generator hebben die de getallen in de Fibonacci-reeks produceert. En we hebben nog een generator voor het kwadrateren van getallen.
Als we de som van de kwadraten van getallen in de Fibonacci-reeks willen weten, kunnen we dat op de volgende manier doen door de output van generatorfuncties samen te pijplijnen.
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))))
Uitvoer
4895
Deze pipelining is efficiënt en gemakkelijk te lezen (en ja, een stuk cooler!).
Python
- Python-operators
- Python-functieargumenten
- Python-woordenboek
- Python-generatoren
- Python-sluitingen
- Python-decorateurs
- Python Lambda-functies met VOORBEELDEN
- Python abs() Functie:Voorbeelden van absolute waarden
- Python round() functie met VOORBEELDEN
- Python map() functie met VOORBEELDEN
- Opbrengst in Python-zelfstudie:voorbeeld van generator en rendement versus rendement