de Radu Raicea

Cum – și de ce – ar trebui să folosiți Python Generators

Cum si de ce ar trebui sa folositi
Credit de imagine: Beat Health Recruitment

Generatoare au fost o parte importantă a Python încă de când au fost introduse cu PEP 255.

Funcțiile generatoare vă permit să declarați o funcție care se comportă ca un iterator.

Acestea permit programatorilor să facă un iterator într-un mod rapid, ușor și curat.

Ce este un iterator, ați putea întreba?

Un iterator este un obiect pe care se poate itera (bucla). Este folosit pentru a abstractiza un container de date pentru a-l face să se comporte ca un obiect iterabil. Probabil că utilizați deja câteva obiecte iterabile în fiecare zi: șiruri, liste și dicționare pentru a numi câteva.

Un iterator este definit de o clasă care implementează Protocolul iterator. Acest protocol caută două metode în cadrul clasei: __iter__ și __next__.

Whoa, pas înapoi. De ce ai vrea să faci chiar iteratori?

Economisirea spațiului de memorie

Iteratorii nu calculează valoarea fiecărui element atunci când sunt instanțiate. Ei îl calculează numai atunci când îl solicitați. Acest lucru este cunoscut sub numele de evaluare leneșă.

Evaluarea leneșă este utilă atunci când aveți un set de date foarte mare de calculat. Vă permite să începeți să utilizați datele imediat, în timp ce întregul set de date este calculat.

Să presupunem că vrem să obținem toate numerele prime care sunt mai mici decât un număr maxim.

Mai întâi definim funcția care verifică dacă un număr este prim:

def check_prime(number):    for divisor in range(2, int(number ** 0.5) + 1):        if number % divisor == 0:            return False    return True

Apoi, definim clasa iteratorului care va include __iter__ și __next__ metode:

class Primes:    def __init__(self, max):        self.max = max        self.number = 1
    def __iter__(self):        return self
    def __next__(self):        self.number += 1        if self.number >= self.max:            raise StopIteration        elif check_prime(self.number):            return self.number        else:            return self.__next__()

Primes este instanțiat cu o valoare maximă. Dacă următorul prim este mai mare sau egal decât max, iteratorul va ridica un StopIteration excepție, care pune capăt iteratorului.

Când solicităm următorul element din iterator, acesta va crește number cu 1 și verificați dacă este un număr prim. Dacă nu este, va suna __next__ din nou până number este prim. Odată ce este, iteratorul returnează numărul.

Prin utilizarea unui iterator, nu creăm o listă de numere prime în memoria noastră. În schimb, generăm următorul număr prim de fiecare dată când solicităm acest lucru.

Să încercăm:

primes = Primes(100000000000)
print(primes)
for x in primes:    print(x)
---------
<__main__.Primes object at 0x1021834a8>235711...

Fiecare iterație a Primes apeluri de obiecte __next__ pentru a genera următorul număr prim.

Iteratorii pot fi iterați o singură dată. Dacă încercați să repetați primes din nou, nicio valoare nu va fi returnată. Se va comporta ca o listă goală.

Acum, că știm ce sunt iteratorii și cum să facem unul, vom trece la generatoare.

Generatoare

Reamintim că funcțiile generatorului ne permit să creăm iteratori într-un mod mai simplu.

Generatorii introduc yield declarație către Python. Funcționează cam așa return deoarece returnează o valoare.

Diferența este că salvează statul a funcției. Data viitoare când funcția este apelată, execuția continuă de la de unde a rămas, cu aceleași valori variabile a avut-o înainte de a ceda.

Dacă ne transformăm Primes iterator într-un generator, va arăta astfel:

def Primes(max):    number = 1    while number < max:        number += 1        if check_prime(number):            yield number
primes = Primes(100000000000)
print(primes)
for x in primes:    print(x)
---------
<generator object Primes at 0x10214de08>235711...

Acum este destul de pitonic! Putem face mai bine?

Da! Putem folosi Generator Expressions, introdus cu PEP 289.

Acesta este echivalentul de înțelegere a listei generatoarelor. Funcționează exact în același mod ca o înțelegere a listei, dar expresia este înconjurată de () spre deosebire de [].

Următoarea expresie poate înlocui funcția noastră de generator de mai sus:

primes = (i for i in range(2, 100000000000) if check_prime(i))
print(primes)
for x in primes:    print(x)
---------
<generator object <genexpr> at 0x101868e08>235711...

Aceasta este frumusețea generatoarelor din Python.

În concluzie…

  • Generatoarele vă permit să creați iteratori într-o manieră foarte pitonică.
  • Iteratorii permit evaluarea leneșă, generând elementul următor al unui obiect iterabil numai atunci când este solicitat. Acest lucru este util pentru seturi de date foarte mari.
  • Iteratoarele și generatoarele pot fi repetate o singură dată.
  • Funcțiile Generator sunt mai bune decât Iterators.
  • Generator Expressions sunt mai bune decât Iterators (numai pentru cazuri simple).

Puteți verifica, de asemenea, my explicaţie despre modul în care am folosit Python pentru a găsi oameni interesanți de urmărit pe Medium.

Pentru mai multe actualizări, urmează-mă Stare de nervozitate.