Výťažnosť Pythonu, generátory a výrazy generátorov

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ť, StopIterationkeď 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 yieldnamiesto príkazu namiesto returnpríkazu.

Ak funkcia obsahuje aspoň jeden yieldpríkaz (môže obsahovať ďalší príkaz yieldalebo returnpríkaz), stane sa funkciou generátora. Oboje yielda returnvráti určitú hodnotu z funkcie.

Rozdiel je v tom, že zatiaľ čo returnpríkaz úplne ukončí funkciu, yieldprí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 yieldprí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 pomocou next().
  • 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á, StopIterationpri ď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 yieldprí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 forslučka zaberá iterátor a iteruje ho pomocou next()funkcie. Po StopIterationzdvihnutí 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!).

Zaujímavé články...