Python-opbrengst, generatoren en generatoruitdrukkingen

In deze tutorial leert u hoe u eenvoudig iteraties kunt maken met Python-generatoren, hoe deze verschilt van iterators en normale functies, en waarom u deze 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, interne toestanden bijhouden en verhogen StopIterationals er geen waarden zijn die kunnen worden geretourneerd.

Dit is zowel langdurig 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 is een generator een functie die een object (iterator) retourneert waarover we kunnen herhalen (één waarde tegelijk).

Maak generatoren in Python

Het is vrij eenvoudig om een ​​generator in Python te maken. Het is net zo eenvoudig als het definiëren van een normale functie, maar met een yieldstatement in plaats van een returnstatement.

Als een functie ten minste één yieldinstructie bevat (het kan andere yieldof returninstructies bevatten ), wordt het een generatorfunctie. Beide yielden returnzullen een waarde van een functie retourneren.

Het verschil is dat terwijl een returninstructie een functie volledig beëindigt, de yieldinstructie de functie pauzeert en al zijn statussen opslaat en later verder gaat met opeenvolgende aanroepen.

Verschillen tussen generatorfunctie en normale functie

Hier is hoe een generatorfunctie verschilt van een normale functie.

  • Generatorfunctie bevat een of meer yieldinstructies.
  • Wanneer het wordt aangeroepen, wordt een object (iterator) geretourneerd maar wordt de uitvoering niet onmiddellijk gestart.
  • Methoden zoals __iter__()en __next__()worden automatisch geïmplementeerd. Dus we kunnen de items doorlopen met next().
  • Zodra de functie het oplevert, wordt de functie gepauzeerd en wordt de controle overgedragen aan de beller.
  • Lokale variabelen en hun status worden tussen opeenvolgende oproepen onthouden.
  • Ten slotte, wanneer de functie wordt beëindigd, StopIterationwordt automatisch verhoogd bij verdere oproepen.

Hier is een voorbeeld om alle bovenstaande punten te illustreren. We hebben een generatorfunctie my_gen()met verschillende yieldinstructies.

 # 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

Hieronder wordt een interactieve run in de tolk 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 tussen elke oproep wordt onthouden.

In tegenstelling tot normale functies worden de lokale variabelen niet vernietigd als de functie oplevert. Bovendien kan het generatorobject slechts één keer worden herhaald.

Om het proces opnieuw te starten, moeten we een ander generatorobject maken met zoiets als a = my_gen().

Een laatste ding om op te merken is dat we generatoren rechtstreeks met for-loops kunnen gebruiken.

Dit komt doordat een forlus een iterator nodig heeft en deze herhaalt met behulp van de next()functie. Het eindigt automatisch wanneer StopIterationwordt verhoogd. Kijk hier om te weten hoe een for-lus daadwerkelijk is geïmplementeerd in 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)

Wanneer u het programma uitvoert, is de uitvoer:

 Dit wordt als eerste geprint 1 Dit wordt als tweede geprint 2 Dit wordt als laatste geprint 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

 olleh

In dit voorbeeld hebben we de range()functie gebruikt om de index in omgekeerde volgorde op te halen met 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 eenvoudig tijdens het gebruik worden gemaakt met behulp van generatoruitdrukkingen. Het maakt het bouwen van generatoren eenvoudig.

Net als de lambda-functies die anonieme functies creëren, creëren generatoruitdrukkingen anonieme generatorfuncties.

De syntaxis voor generatoruitdrukking is vergelijkbaar met die van lijstbegrip in Python. Maar de vierkante haken worden 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.

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

Generatoren zijn uitstekende media om een ​​oneindige stroom gegevens weer te geven. Oneindige stromen kunnen niet in het geheugen worden opgeslagen, en aangezien generatoren slechts één item per keer produceren, kunnen ze een oneindige stroom gegevens vertegenwoordigen.

De volgende generatorfunctie kan alle even getallen genereren (tenminste 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 pijpleidingen. 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 achterhalen, kunnen we dit op de volgende manier doen door de uitvoer van generatorfuncties samen te pipelineeren.

 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 af te lezen (en ja, veel cooler!).

Interessante artikelen...