Python-avkastning, generatorer och generatoruttryck

I den här handledningen lär du dig hur du enkelt skapar iterationer med Python-generatorer, hur det skiljer sig från iteratorer och normala funktioner och varför du ska använda det.

Video: Python Generators

Generatorer i Python

Det finns mycket arbete med att bygga en iterator i Python. Vi måste implementera en klass med __iter__()och __next__()metod, hålla reda på interna tillstånd och höja StopIterationnär det inte finns några värden som ska returneras.

Detta är både långt och kontraintuitivt. Generatorn kommer till undsättning i sådana situationer.

Python-generatorer är ett enkelt sätt att skapa iteratorer. Allt arbete vi nämnde ovan hanteras automatiskt av generatorer i Python.

Enkelt sagt är en generator en funktion som returnerar ett objekt (iterator) som vi kan itera över (ett värde i taget).

Skapa generatorer i Python

Det är ganska enkelt att skapa en generator i Python. Det är lika enkelt som att definiera en normal funktion, men med ett yielduttalande istället för ett returnuttalande.

Om en funktion innehåller minst en yieldsats (den kan innehålla andra yieldeller returnsatser) blir den en generatorfunktion. Båda yieldoch returnkommer att returnera något värde från en funktion.

Skillnaden är att medan ett returnuttalande avslutar en funktion helt, yieldpausar uttalandet funktionen och sparar alla dess tillstånd och fortsätter senare därifrån på successiva samtal.

Skillnader mellan generatorfunktion och normalfunktion

Så här skiljer sig en generatorfunktion från en normal funktion.

  • Generatorfunktionen innehåller en eller flera yieldpåståenden.
  • När det anropas returnerar det ett objekt (iterator) men startar inte körningen omedelbart.
  • Metoder som __iter__()och __next__()implementeras automatiskt. Så vi kan itera igenom objekten med next().
  • När funktionen har gett pausas funktionen och kontrollen överförs till den som ringer.
  • Lokala variabler och deras tillstånd kommer ihåg mellan på varandra följande samtal.
  • Slutligen, när funktionen avslutas, StopIterationhöjs automatiskt vid ytterligare samtal.

Här är ett exempel för att illustrera alla punkter som anges ovan. Vi har en generatorfunktion my_gen()med flera yielduttalanden.

 # 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örning i tolken ges nedan. Kör dessa i Python-skalet för att se utdata.

 >>> # 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 intressant sak att notera i exemplet ovan är att värdet på variabeln kommer ihåg mellan varje samtal.

Till skillnad från normala funktioner förstörs inte de lokala variablerna när funktionen ger. Dessutom kan generatorobjektet endast upprepas en gång.

För att starta om processen måste vi skapa ett annat generatorobjekt med något liknande a = my_gen().

En sista sak att notera är att vi kan använda generatorer med för loopar direkt.

Detta beror på att en forslinga tar en iterator och itererar över den med hjälp av next()funktionen. Det slutar automatiskt när det StopIterationhöjs. Kolla här för att veta hur en for-loop faktiskt implementeras 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ör programmet blir resultatet:

 Detta trycks först 1 Detta trycks andra 2 Detta skrivs ut senast 3

Python Generators with a Loop

Ovanstående exempel har mindre användning och vi studerade det bara för att få en uppfattning om vad som hände i bakgrunden.

Generellt implementeras generatorfunktioner med en slinga som har ett lämpligt avslutande tillstånd.

Låt oss ta ett exempel på en generator som reverserar en sträng.

 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 det här exemplet har vi använt range()funktionen för att få index i omvänd ordning med hjälp av for-slingan.

Obs! Den här generatorfunktionen fungerar inte bara med strängar utan också med andra typer av iterables som lista, tupel etc.

Python Generator Expression

Enkla generatorer kan enkelt skapas direkt med generatoruttryck. Det gör det enkelt att bygga generatorer.

På samma sätt som lambdafunktionerna som skapar anonyma funktioner skapar generatoruttryck anonyma generatorfunktioner.

Syntaxen för generatoruttryck liknar den för listförståelsen i Python. Men de hakparenteserna ersätts med runda parenteser.

Den största skillnaden mellan en listförståelse och ett generatoruttryck är att en listförståelse producerar hela listan medan generatoruttrycket producerar ett objekt i taget.

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 är utmärkta medier för att representera en oändlig dataström. Oändliga strömmar kan inte lagras i minnet och eftersom generatorer bara producerar ett objekt åt gången kan de representera en oändlig dataström.

Följande generatorfunktion kan generera alla jämna siffror (åtminstone i teorin).

 def all_even(): n = 0 while True: yield n n += 2

4. Rörledningsgeneratorer

Flera generatorer kan användas för att leda en serie operationer. Detta illustreras bäst med ett exempel.

Anta att vi har en generator som producerar siffrorna i Fibonacci-serien. Och vi har en annan generator för kvadrering av nummer.

Om vi ​​vill ta reda på summan av siffror i Fibonacci-serien kan vi göra det på följande sätt genom att pipelina utmatningen av generatorfunktioner tillsammans.

 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

Denna rörledning är effektiv och lättläst (och ja, mycket svalare!).

Intressanta artiklar...