Python hozam, generátorok és generátor kifejezések

Ebben az oktatóanyagban megtudhatja, hogyan lehet könnyen létrehozni iterációkat a Python generátorok segítségével, miben különbözik az iterátoroktól és a normál függvényektől, és miért kell használni.

Videó: Python generátorok

Generátorok a Pythonban

Nagyon sok munka van egy iterátor felépítésével a Pythonban. Meg kell valósítanunk egy osztályt __iter__()és __next__()módszert, nyomon kell követnünk a belső állapotokat, és fel kell emelnünk, StopIterationha nincsenek visszaadandó értékek.

Ez egyszerre hosszú és ellentmondásos. A Generator ilyen helyzetekben segít.

A Python generátorok az iterátorok létrehozásának egyszerű módja. A fent említett összes munkát a generátorok automatikusan kezelik a Pythonban.

Egyszerűen szólva, a generátor egy olyan függvény, amely egy olyan objektumot (iterátort) ad vissza, amelyet iterálni tudunk (egyszerre egy értéket).

Generátorok létrehozása a Pythonban

Generátor létrehozása Pythonban meglehetősen egyszerű. Olyan egyszerű, mint egy normál függvény meghatározása, de yieldállítás helyett returnutasítással.

Ha egy függvény tartalmaz legalább egy yieldutasítást (tartalmazhat másokat yieldvagy returnutasításokat), akkor generátorfüggvénysé válik. Mindkettő, yieldés returnvisszaad valamilyen értéket egy függvényből.

A különbség az, hogy míg egy returnutasítás teljes egészében megszünteti a függvényt, addig az yieldutasítás szünetelteti a függvény összes állapotát, és később onnan folytatja egymást követő hívásokon.

A generátor és a normál funkció közötti különbségek

Így különbözik a generátorfüggvény a normál függvénytől.

  • A Generator függvény egy vagy több yieldutasítást tartalmaz.
  • Híváskor visszaad egy objektumot (iterátort), de nem indítja el azonnal a végrehajtást.
  • Az olyan módszerek, mint a __iter__()és __next__()automatikusan megvalósulnak. Így iterálni tudunk a tételek segítségével next().
  • Amint a függvény megadja, a funkció szünetel, és a vezérlés átkerül a hívónak.
  • Az egymást követő hívások között a helyi változókra és állapotukra emlékezünk.
  • Végül, amikor a funkció megszűnik, StopIterationa további hívások automatikusan felemelkednek.

Íme egy példa az összes fentebb említett pont bemutatására. Van egy generátorfüggvényünk, my_gen()amelyet több yieldállítással nevezünk meg.

 # 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

Az interaktív futtatás a tolmácsban alább látható. Futtassa ezeket a Python héjban a kimenet megtekintéséhez.

 >>> # 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

Egy érdekes dolog, amit a fenti példában meg kell jegyezni, hogy az n változó értéke minden hívás között megmarad.

A normál függvényekkel ellentétben a helyi változók nem pusztulnak el, amikor a függvény megadja. Ezenkívül a generátor objektum csak egyszer ismételhető.

A folyamat újraindításához létre kell hoznunk egy másik generátorobjektumot valami hasonló használatával a = my_gen().

Végül meg kell jegyezni, hogy a generátorokat közvetlenül használhatjuk hurkokhoz.

Ez azért van, mert egy forhurok iterátort vesz fel, és a next()funkció segítségével iterál rajta . Emeléskor automatikusan véget ér StopIteration. Itt ellenőrizheti, hogy a for ciklus hogyan valósul meg valójában a Pythonban.

 # 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)

A program futtatásakor a kimenet a következő lesz:

 Ezt nyomtatják először 1 Ezt nyomtatják másodszor 2 Ezt nyomtatják végül 3-ra

Python generátorok hurokkal

A fenti példa kevésbé hasznos, és csak azért tanulmányoztuk, hogy képet kapjunk arról, mi történik a háttérben.

Normális esetben a generátor funkcióit egy megfelelő befejező feltételű hurokkal hajtják végre.

Vegyünk egy példát egy generátorra, amely megfordítja a karakterláncot.

 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)

Kimenet

 olleh

Ebben a példában a range()függvényt használtuk arra, hogy az indexet fordított sorrendben kapjuk meg a for ciklus segítségével.

Megjegyzés : Ez a generátor funkció nemcsak karakterláncokkal működik, hanem más típusú iterable-kkel is, például listával, duplával stb.

Python Generator Expression

Az egyszerű generátorok menet közben könnyen létrehozhatók generátor kifejezések segítségével. Ez megkönnyíti a generátorok építését.

A névtelen függvényeket létrehozó lambda függvényekhez hasonlóan a generátor kifejezések is névtelen generátor funkciókat hoznak létre.

A generátor kifejezés szintaxisa hasonló a Pythonban található listamegértéshez. De a szögletes zárójeleket kerek zárójelek helyettesítik.

A lista megértése és a generátor kifejezés közötti legfőbb különbség az, hogy a lista megértése a teljes listát állítja elő, míg a generátor kifejezés egyszerre egy elemet állít elő.

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

A generátorok kiváló médiumok, amelyek végtelen adatfolyamot képviselnek. A végtelen adatfolyamokat nem lehet a memóriában tárolni, és mivel a generátorok egyszerre csak egy elemet állítanak elő, végtelen adatfolyamot jelenthetnek.

A következő generátorfüggvény előállíthatja az összes páros számot (legalábbis elméletben).

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

4. Csővezeték generátorok

Több generátor használható műveletek sorozatának vezetésére. Ezt legjobban egy példával szemléltethetjük.

Tegyük fel, hogy van egy generátorunk, amely előállítja a Fibonacci sorozat számait. És van még egy generátorunk a négyzetek számozására.

Ha meg akarjuk tudni a Fibonacci sorozat szám négyzetének összegét, akkor a következő módon tehetjük meg a generátorfüggvények kimenetének összekapcsolásával.

 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))))

Kimenet

 4895

Ez a csővezeték hatékony és könnyen olvasható (és igen, sokkal hűvösebb!).

érdekes cikkek...