Randament Python, Generatoare și Expresii Generator

În acest tutorial, veți învăța cum să creați cu ușurință iterații folosind generatoare Python, cum este diferit de iteratoare și funcțiile normale și de ce ar trebui să-l utilizați.

Video: Generatoare Python

Generatoare în Python

Există multă muncă în construirea unui iterator în Python. Trebuie să implementăm o clasă cu __iter__()și o __next__()metodă, să urmărim stările interne și să creștem StopIterationatunci când nu există valori de returnat.

Acest lucru este atât lung cât și contraintuitiv. Generatorul vine în ajutor în astfel de situații.

Generatoarele Python sunt un mod simplu de a crea iteratoare. Toate lucrările menționate mai sus sunt gestionate automat de generatoare în Python.

Pur și simplu vorbind, un generator este o funcție care returnează un obiect (iterator) pe care îl putem itera (o valoare la un moment dat).

Creați generatoare în Python

Este destul de simplu să creați un generator în Python. Este la fel de ușor ca definirea unei funcții normale, dar cu o yieldafirmație în loc de o returnafirmație.

Dacă o funcție conține cel puțin o yieldinstrucțiune (poate conține alte instrucțiuni yieldsau returninstrucțiuni), devine o funcție generator. Ambele yieldși returnvor returna o anumită valoare dintr-o funcție.

Diferența este că, în timp ce o returninstrucțiune termină complet o funcție, yieldinstrucțiunea întrerupe funcția salvând toate stările sale și ulterior continuă de acolo pe apeluri succesive.

Diferențele dintre funcția Generator și funcția Normal

Iată cum diferă o funcție generator de o funcție normală.

  • Funcția Generator conține una sau mai multe yieldinstrucțiuni.
  • Când este apelat, returnează un obiect (iterator), dar nu începe executarea imediat.
  • Metode precum __iter__()și __next__()sunt implementate automat. Deci, putem itera prin elemente folosind next().
  • Odată ce funcția cedează, funcția este întreruptă și controlul este transferat către apelant.
  • Variabilele locale și stările lor sunt memorate între apeluri succesive.
  • În cele din urmă, când funcția se termină, StopIterationeste ridicată automat la apeluri suplimentare.

Iată un exemplu pentru a ilustra toate punctele enunțate mai sus. Avem o funcție generator denumită my_gen()cu mai multe yieldinstrucțiuni.

 # 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

O rundă interactivă în interpret este prezentată mai jos. Rulați acestea în shell-ul Python pentru a vedea ieșirea.

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

Un lucru interesant de menționat în exemplul de mai sus este că valoarea variabilei n este amintită între fiecare apel.

Spre deosebire de funcțiile normale, variabilele locale nu sunt distruse atunci când funcția cedează. Mai mult, obiectul generator poate fi iterat o singură dată.

Pentru a reporni procesul, trebuie să creăm un alt obiect generator folosind ceva de genul a = my_gen().

Un ultim lucru de remarcat este că putem folosi generatoare cu bucle direct.

Acest lucru se datorează faptului că o forbuclă preia un iterator și o repetă folosind next()funcția. Se termină automat când StopIterationeste ridicat. Verificați aici pentru a afla cum este implementată de fapt o buclă for în 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)

Când rulați programul, ieșirea va fi:

 Acesta este tipărit primul 1 Acesta este tipărit al doilea 2 Acesta este tipărit în sfârșit 3

Generatoare Python cu buclă

Exemplul de mai sus este mai puțin util și l-am studiat doar pentru a ne face o idee despre ceea ce se întâmpla în fundal.

În mod normal, funcțiile generatorului sunt implementate cu o buclă având o condiție de terminare adecvată.

Să luăm un exemplu de generator care inversează un șir.

 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)

Ieșire

 olleh

În acest exemplu, am folosit range()funcția pentru a obține indexul în ordine inversă folosind bucla for.

Notă : Această funcție generator nu funcționează numai cu șiruri, ci și cu alte tipuri de iterabile, cum ar fi listă, tuplu etc.

Expresia Python Generator

Generatoarele simple pot fi create cu ușurință din mers folosind expresii generatoare. Face ușor construirea generatoarelor.

Similar cu funcțiile lambda care creează funcții anonime, expresiile generator creează funcții generatoare anonime.

Sintaxa pentru expresia generatorului este similară cu cea a unei înțelegeri a listei în Python. Dar parantezele pătrate sunt înlocuite cu paranteze rotunde.

Diferența majoră dintre o înțelegere a listei și o expresie generatoare este că o înțelegere a listei produce întreaga listă, în timp ce expresia generatoare produce un articol la un moment dat.

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

Generatoarele sunt medii excelente pentru a reprezenta un flux infinit de date. Fluxurile infinite nu pot fi stocate în memorie și, din moment ce generatoarele produc doar un singur articol la un moment dat, ele pot reprezenta un flux infinit de date.

Următoarea funcție generator poate genera toate numerele pare (cel puțin teoretic).

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

4. Generatoare de conducte

Mai multe generatoare pot fi utilizate pentru a conducta o serie de operații. Acest lucru este cel mai bine ilustrat folosind un exemplu.

Să presupunem că avem un generator care produce numerele din seria Fibonacci. Și avem un alt generator pentru pătrarea numerelor.

Dacă vrem să aflăm suma pătratelor de numere din seria Fibonacci, o putem face în felul următor prin linia de ieșire a funcțiilor generatorului împreună.

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

Ieșire

 4895

Această conductă este eficientă și ușor de citit (și da, mult mai cool!).

Articole interesante...