Prinos Pythona, generatori i izrazi generatora

U ovom vodiču naučit ćete kako lako stvoriti iteracije pomoću Python generatora, kako se razlikuje od iteratora i normalnih funkcija i zašto biste ga trebali koristiti.

Video: Python generatori

Generatori u Pythonu

Puno je posla u izgradnji iteratora u Pythonu. Moramo implementirati klasu s __iter__()i __next__()metodom, pratiti unutarnja stanja i podizati StopIterationkada nema vrijednosti koje treba vratiti.

Ovo je i dugotrajno i kontintuitivno. Generator dolazi u pomoć u takvim situacijama.

Python generatori jednostavan su način stvaranja iteratora. Sav posao koji smo gore spomenuli automatski obrađuju generatori u Pythonu.

Jednostavno rečeno, generator je funkcija koja vraća objekt (iterator) koji možemo ponoviti (po jednu vrijednost).

Stvorite generatore u Pythonu

Stvoriti generator u Pythonu prilično je jednostavno. Jednostavno je kao definirati normalnu funkciju, ali s yieldiskazom umjesto returniskazom.

Ako funkcija sadrži barem jedan yieldizraz (može sadržavati i drugi yieldili returniskaz), ona postaje funkcija generatora. Oba yieldi returnvratit će neku vrijednost iz funkcije.

Razlika je u tome što dok returnizraz u potpunosti završava funkciju, yieldizraz zaustavlja funkciju spremajući sva njena stanja i kasnije nastavlja odatle na uzastopne pozive.

Razlike između funkcije generatora i normalne funkcije

Evo kako se funkcija generatora razlikuje od normalne funkcije.

  • Funkcija generatora sadrži jedan ili više yieldizraza.
  • Kada se pozove, vraća objekt (iterator), ali ne započinje izvršenje odmah.
  • Metode se poput __iter__()i __next__()implementiraju automatski. Tako možemo pregledavati stavke pomoću next().
  • Nakon što funkcija popusti, funkcija se pauzira i kontrola se prenosi na pozivatelja.
  • Lokalne varijable i njihova stanja pamte se između uzastopnih poziva.
  • Konačno, kada se funkcija završi, StopIterationautomatski se podiže na daljnje pozive.

Evo primjera koji ilustrira sve gore navedene točke. Imamo funkciju generatora imenovanu my_gen()s nekoliko yieldizraza.

 # 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

Interaktivni rad u interpretatoru dat je u nastavku. Pokrenite ih u ljusci Python da biste vidjeli izlaz.

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

Jedna zanimljiva stvar koju treba primijetiti u gornjem primjeru je da se vrijednost varijable n pamti između svakog poziva.

Za razliku od normalnih funkcija, lokalne varijable se ne uništavaju kada funkcija popusti. Nadalje, objekt generatora može se ponoviti samo jednom.

Da bismo ponovno pokrenuli postupak, moramo stvoriti još jedan objekt generatora koristeći nešto poput a = my_gen().

Posljednja stvar koju treba napomenuti jest da generatore for for možemo koristiti izravno.

To je zato što forpetlja uzima iterator i prevlači se preko njega pomoću next()funkcije. Automatski završava kad StopIterationse podigne. Ovdje provjerite kako se for petlja zapravo implementira u 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)

Kada pokrenete program, izlaz će biti:

 Ovo se prvo ispisuje 1 Ovo se ispisuje drugo 2 Ovo se ispisuje konačno 3

Python generatori s petljom

Gornji je primjer manje koristan i mi smo ga proučavali samo da bismo imali predodžbu o tome što se događa u pozadini.

Obično se funkcije generatora provode s petljom koja ima odgovarajuće završno stanje.

Uzmimo primjer generatora koji obrće niz.

 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)

Izlaz

 olleh

U ovom smo primjeru koristili range()funkciju za dobivanje indeksa obrnutim redoslijedom pomoću petlje for.

Napomena : Ova funkcija generatora ne radi samo sa nizovima, već i s drugim vrstama iterabila poput popisa, korice itd.

Izraz generatora Pythona

Jednostavni generatori mogu se lako stvoriti u hodu pomoću izraza generatora. To olakšava izgradnju generatora.

Slično lambda funkcijama koje stvaraju anonimne funkcije, izrazi generatora stvaraju anonimne funkcije generatora.

Sintaksa izraza generatora slična je sintaksi razumijevanja popisa u Pythonu. Ali uglate zagrade zamjenjuju se okruglim zagradama.

Glavna razlika između razumijevanja popisa i izraza generatora je u tome što razumijevanje popisa stvara cijeli popis, dok izraz izraza daje jednu po jednu stavku.

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

Generatori su izvrsni mediji koji predstavljaju beskonačan tok podataka. Beskonačni tokovi ne mogu se pohraniti u memoriju, a budući da generatori istodobno proizvode samo po jednu stavku, mogu predstavljati beskonačan tok podataka.

Sljedeća funkcija generatora može generirati sve parne brojeve (barem u teoriji).

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

4. Cjevovodni generatori

Za generiranje niza operacija može se koristiti više generatora. To je najbolje ilustrirati na primjeru.

Pretpostavimo da imamo generator koji stvara brojeve u Fibonaccijevoj seriji. I imamo još jedan generator za kvadriranje brojeva.

Ako želimo saznati zbroj kvadrata brojeva u Fibonaccijevoj seriji, to možemo učiniti na sljedeći način cjevovodom zajedno izlaza funkcija generatora.

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

Izlaz

 4895

Ovaj cjevovod je učinkovit i lak za čitanje (i da, puno je hladniji!).

Zanimljivi članci...