V tomto výučbe sa dozviete, ako ľahko vytvárať iterácie pomocou generátorov Pythonu, ako sa líši od iterátorov a bežných funkcií a prečo by ste ich mali používať.
Video: Generátory Pythonu
Generátory v Pythone
Pri vytváraní iterátora v Pythone je veľa práce. Musíme implementovať triedu s metódou __iter__()
a __next__()
metódou, sledovať vnútorné stavy a zvyšovať, StopIteration
keď nie sú k dispozícii žiadne hodnoty, ktoré sa majú vrátiť.
Je to zdĺhavé a neintuitívne. V takýchto situáciách prichádza na pomoc generátor.
Generátory Pythonu sú jednoduchý spôsob vytvárania iterátorov. Všetka práca, ktorú sme spomenuli vyššie, je automaticky spracovaná generátormi v Pythone.
Zjednodušene povedané, generátor je funkcia, ktorá vracia objekt (iterátor), nad ktorým môžeme iterovať (vždy po jednej hodnote).
Vytvorte generátory v Pythone
Vytvorenie generátora v Pythone je pomerne jednoduché. Je to také ľahké ako definovať normálnu funkciu, ale yield
namiesto príkazu namiesto return
príkazu.
Ak funkcia obsahuje aspoň jeden yield
príkaz (môže obsahovať ďalší príkaz yield
alebo return
príkaz), stane sa funkciou generátora. Oboje yield
a return
vráti určitú hodnotu z funkcie.
Rozdiel je v tom, že zatiaľ čo return
príkaz úplne ukončí funkciu, yield
príkaz pozastaví funkciu uložením všetkých jej stavov a neskôr odtiaľ pokračuje pri následných hovoroch.
Rozdiely medzi funkciou generátora a normálnou funkciou
Tu je ukážka toho, ako sa funkcia generátora líši od normálnej funkcie.
- Funkcia generátora obsahuje jeden alebo viac
yield
príkazov. - Pri volaní vráti objekt (iterátor), ale nezačne okamžite.
- Metódy sa páčia
__iter__()
a__next__()
implementujú sa automaticky. Takže môžeme iterovať pomocou položiek pomocounext()
. - Len čo sa funkcia podarí, funkcia sa pozastaví a riadenie sa prenesie na volajúceho.
- Miestne premenné a ich stavy si pamätajú medzi nasledujúcimi hovormi.
- Nakoniec, keď je funkcia ukončená,
StopIteration
pri ďalších hovoroch sa automaticky vyvolá.
Tu je príklad ilustrujúci všetky body uvedené vyššie. Máme funkciu generátora pomenovanú my_gen()
niekoľkými yield
príkazmi.
# 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
Interaktívny beh tlmočníka je uvedený nižšie. Spustite ich v prostredí Pythonu, aby ste videli výstup.
>>> # 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
Vo vyššie uvedenom príklade je potrebné poznamenať, že hodnota premennej n sa pamätá medzi jednotlivými hovormi.
Na rozdiel od normálnych funkcií sa lokálne premenné pri výťažku funkcie nezničia. Objekt generátora možno ďalej iterovať iba raz.
Na opätovné spustenie procesu musíme vytvoriť ďalší objekt generátora pomocou niečoho podobného a = my_gen()
.
Posledná vec, ktorú si treba uvedomiť, je, že môžeme použiť priamo generátory s pre slučky.
Je to tak preto, lebo for
slučka zaberá iterátor a iteruje ho pomocou next()
funkcie. Po StopIteration
zdvihnutí sa automaticky končí . Skontrolujte tu, aby ste zistili, ako je slučka for v skutočnosti implementovaná v Pythone.
# 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)
Po spustení programu bude výstup:
Toto sa vytlačí ako prvé 1 Toto sa vytlačí ako druhé 2 Toto sa vytlačí ako posledné 3
Generátory Pythonu so slučkou
Vyššie uvedený príklad je menej užitočný a študovali sme ho iba preto, aby sme získali predstavu o tom, čo sa deje v pozadí.
Normálne sú funkcie generátora implementované so slučkou, ktorá má vhodný koncový stav.
Zoberme si príklad generátora, ktorý obráti reťazec.
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)
Výkon
olleh
V tomto príklade sme použili range()
funkciu na získanie indexu v opačnom poradí pomocou slučky for.
Poznámka : Táto funkcia generátora pracuje nielen s reťazcami, ale aj s inými druhmi opakovateľných položiek, ako sú zoznam, n-tica atď.
Výraz generátora Pythonu
Jednoduché generátory je možné ľahko vytvárať za behu pomocou výrazov generátorov. Uľahčuje vytváranie generátorov.
Podobne ako funkcie lambda, ktoré vytvárajú anonymné funkcie, vytvárajú výrazy generátora anonymné funkcie generátora.
Syntax pre výraz generátora je podobná syntaxe s porozumením zoznamu v Pythone. Ale hranaté zátvorky sú nahradené okrúhlymi zátvorkami.
Hlavný rozdiel medzi porozumením zoznamu a výrazom generátora je v tom, že s porozumením zoznamu vznikne celý zoznam, zatiaľ čo výraz generátora vytvorí jednu položku naraz.
Majú lenivé prevedenie (vyrábajú predmety iba na požiadanie). Z tohto dôvodu je výraz generátora oveľa efektívnejší v pamäti ako ekvivalentné porozumenie zoznamu.
# 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)
Výkon
(1, 9, 36, 100)
Vyššie vidíme, že výraz generátora nepriniesol požadovaný výsledok okamžite. Namiesto toho vrátil objekt generátora, ktorý vyrába položky iba na požiadanie.
Takto môžeme začať dostávať položky z generátora:
# 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)
Keď spustíme vyššie uvedený program, dostaneme nasledujúci výstup:
1 9 36 100 Traceback (posledný posledný hovor): Súbor „“, riadok 15, v StopIteration
Ako argumenty funkcie je možné použiť výrazy generátora. Ak sa použije takýmto spôsobom, okrúhle zátvorky sa dajú zrušiť.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Používanie generátorov Pythonu
Existuje niekoľko dôvodov, vďaka ktorým sú generátory výkonnou implementáciou.
1. Ľahko sa implementuje
Generátory je možné implementovať jasným a stručným spôsobom v porovnaní s ich náprotivkom triedy iterátorov. Nasleduje príklad na implementáciu postupnosti výkonu 2 pomocou triedy iterátorov.
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
Vyššie uvedený program bol zdĺhavý a mätúci. Teraz urobme to isté pomocou funkcie generátora.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Pretože generátory sledujú podrobnosti automaticky, implementácia bola stručná a oveľa čistejšia.
2. Efektívna pamäť
Normálna funkcia na vrátenie sekvencie vytvorí pred vrátením výsledku celú sekvenciu v pamäti. Ak je počet položiek v poradí veľmi veľký, jedná sa o nadmernú prácu.
Generátorová implementácia takýchto sekvencií je vhodná pre pamäť a je preferovaná, pretože produkuje iba jednu položku súčasne.
3. Predstavujte nekonečný prúd
Generátory sú vynikajúce médiá, ktoré predstavujú nekonečný prúd dát. Nekonečné prúdy nemožno uložiť do pamäte a keďže generátory vytvárajú iba jednu položku súčasne, môžu predstavovať nekonečný prúd údajov.
Nasledujúca funkcia generátora môže generovať všetky párne čísla (aspoň teoreticky).
def all_even(): n = 0 while True: yield n n += 2
4. Potrubné generátory
Na uskutočnenie viacerých operácií je možné použiť viac generátorov. Najlepšie to ilustrujeme na príklade.
Predpokladajme, že máme generátor, ktorý produkuje čísla v rade Fibonacci. A máme ďalší generátor na druhé mocnenie čísel.
Ak chceme zistiť súčet druhých mocnín čísel v Fibonacciho rade, môžeme to urobiť nasledujúcim spôsobom tak, že zreťazíme výstup funkcií generátora.
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))))
Výkon
4895
Toto pipeline je efektívne a ľahko čitateľné (a áno, oveľa chladnejšie!).