Fantastični iteratori i kako ih napraviti

Fotografija Johna Matychcha na Unsplash-u

Problem

Tijekom učenja u Make School-u vidio sam kako moji vršnjaci pišu funkcije koje stvaraju popise predmeta.

s = 'baacabcaab'
p = 'a'
def find_char (string, znak):
  indeksi = popis ()
  za indeks, str_char u nabrajanju (niz):
    ako je str_char == znak:
      indices.append (indeks)
  indeksi povrata
ispis (find_char (s, p)) # [1, 2, 4, 7, 8]

Ova implementacija funkcionira, ali ima nekoliko problema:

  • Što ako želimo samo prvi rezultat; hoćemo li trebati napraviti potpuno novu funkciju?
  • Što ako sve što radimo jednom prelazimo u rezultat, trebamo li svaki element pohraniti u memoriju?

Iteratori su idealno rješenje za ove probleme. Oni funkcioniraju poput "lijenih popisa" u tome što umjesto da vraćaju popis sa svakom vrijednošću koja stvara i vraća svaki element pojedinačno.

Iteratori lijeno vraćaju vrijednosti; štedi memoriju.

Pa, zaronimo u učenje o njima!

Ugrađeni iteratori

Ponovljeni iteratori su najčešće nabrajanje () i zip (). Obje ove lijeno vraćaju vrijednosti slijedeći () s njima.

raspon (), međutim, nije iterator, već "lijeni iterabilni". - Objašnjenje

Pomoću iter () možemo pretvoriti range () u iterator, pa ćemo to učiniti za naše primjere radi učenja.

my_iter = iter (raspon (10))
ispis (sljedeći (moj_iter)) # 0
ispis (sljedeći (moj_iter)) # 1

Nakon svakog poziva next () dobivamo sljedeću vrijednost u našem rasponu; ima li smisla? Ako ga želite pretvoriti u popis, samo mu dajte konstruktor popisa.

my_iter = iter (raspon (10))
ispis (popis (moj_iter)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ako oponašamo takvo ponašanje, počet ćemo razumjeti više o tome kako rade iteratori.

my_iter = iter (raspon (10))
my_list = list ()
probati:
  dok Istina:
    my_list.append (sljedeća (my_iter))
osim StopIteration:
  proći
ispis (moj_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Možete vidjeti da smo ga trebali zamotati u izjavu o pokušaju ulova. To je zato što iteratori podižu StopIteration kad su bili iscrpljeni.

Ako sljedeći broj nazovemo iscrpljeni iterator raspona, dobit ćemo tu pogrešku.

sljedeći (moj_iter) # povišice: zaustavljanje

Izrada iteratora

Pokušajmo napraviti iterator koji se ponaša poput raspona samo sa argumentom stop koristeći tri uobičajena tipa iteratora: klase, funkcije generatora (prinos) i izrazi generatora

klasa

Stari način stvaranja iteratora bio je kroz izričito definiranu klasu. Da bi objekt bio iterator, on mora implementirati __iter __ () koji se vraća i __next __ () koji vraća sljedeću vrijednost.

klasa my_range:
  _current = -1
  def __init __ (samo, zaustavljanje):
    self._stop = stajanje
  def __iter __ (samo):
    vratiti se
  def __next __ (samo):
    self._current + = 1
    ako je self._current> = self._stop:
      povisite StopIteration
    vratiti se._ trenutni
r = my_range (10)
ispis (popis (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

To nije bilo previše teško, ali nažalost, moramo pratiti varijable između poziva next (). Osobno mi se ne sviđa ploča za grijanje ili promjena načina razmišljanja o petljama, jer to nije rješenje za padajuće uređaje, zato više volim generatore

Glavna prednost je što možemo dodati dodatne funkcije koje mijenjaju njegove interne varijable, poput _stop ili stvaraju nove iteratore.

Iteratori klase imaju slabu stranu potrebe za kotlovskom pločom, no mogu imati i dodatne funkcije koje mijenjaju stanje.

Generatori

PEP 255 uveo je "jednostavne generatore" koristeći ključnu riječ prinos.

Danas su generatori iteratori koje je jednostavno napraviti nego njihovi kolege iz klase.

Funkcija generatora

Funkcije generatora su ono o čemu se na kraju raspravljalo u tom PEP-u i moja su omiljena vrsta iteratora, pa krenimo s tim.

def my_range (zaustavljanje):
  indeks = 0
  dok je indeks 
r = my_range (10)
ispis (popis (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Vidite li kako su lijepe one 4 retke koda? To je nešto znatno kraće od implementacije našeg popisa za nadoknadu!

Generator djeluje na iteratorima s manje kotlovske ploče od klasa s normalnim logičkim protokom.

Generator funkcionira automatski "zaustavi" izvršavanje i vrati zadanu vrijednost sa svakim pozivom next (). To znači da se do prvog sljedećeg () poziva ne pokreće nikakav kôd.

To znači da je protok ovako:

  1. next () se zove,
  2. Kôd se izvršava do sljedeće izjave o prinosu.
  3. Vraća se vrijednost na desnoj strani prinosa.
  4. Izvršenje je pauzirano.
  5. Ponovite 1–5 za svaki sljedeći () poziv dok se ne pritisne zadnji redak koda.
  6. StopIteracija je podignuta.

Funkcije generatora omogućuju vam i korištenje prinosa ključne riječi koji sljedeći () pozivi na drugi iterabilni sve dok navedeni iterable ne bude iscrpljen.

def yielded_range ():
  prinos od my_range (10)
ispis (popis (yielded_range ())) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

To nije bio osobito složen primjer. Ali to čak možete učiniti i rekurzivno!

def my_range_recursive (stop, struja = 0):
  ako je struja> = stop:
    povratak
  prinosna struja
  prinos od my_range_recursive (stop, struja + 1)
r = my_range_recursive (10)
ispis (popis (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Izraz generatora

Izrazi generatora omogućuju nam stvaranje iteratora kao jednoslojne mreže i dobri su kad nam ne trebaju dodijeliti vanjske funkcije. Nažalost, ne možemo napraviti drugi my_range koristeći izraz, ali možemo raditi na iterablesima poput naše posljednje my_range funkcije.

my_doubled_range_10 = (x * 2 za x u my_range (10))
ispis (popis (my_doubled_range_10)) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Kul stvar u tome je što čini sljedeće:

  1. Popis pita my_doubled_range_10 za sljedeću vrijednost.
  2. my_doubled_range_10 pita my_range za sljedeću vrijednost.
  3. my_doubled_range_10 vraća vrijednost my_range pomnoženo sa 2.
  4. Popis dodaje vrijednost sebi.
  5. 1–5 ponavljajte dok my_doubled_range_10 ne pokrene StopIteration koja se događa kada my_range učini.
  6. Popis se vraća sa svakom vrijednošću koju je vratio my_doubled_range.

Čak možemo filtrirati pomoću generacijskih izraza!

my_even_range_10 = (x za x u my_range (10) ako x% 2 == 0)
ispis (popis (my_even_range_10)) # [0, 2, 4, 6, 8]

To je vrlo slično prethodnom, osim što my_even_range_10 vraća samo vrijednosti koje odgovaraju zadanom stanju, tako da su samo u vrijednostima samo u vrijednosti [0, 10).

Kroz sve to stvaramo samo popis jer smo mu to rekli.

Prednost

Izvor

Budući da su generatori iteratori, iteratori su iterabilni, a iteratori lijeno vraćaju vrijednosti. To znači da pomoću ovog znanja možemo stvoriti predmete koji će nam dati predmete samo kada ih tražimo i koliko god nam se sviđa.

To znači da možemo proslijediti generatore u funkcije koje se međusobno smanjuju.

ispis (zbroj (my_range (10))) # 45

Izračunavanje iznosa na ovaj način izbjegava stvaranje popisa kada sve što radimo je da ih zbrojimo i zatim odbacimo.

Možemo ponovno napisati prvi primjer da bismo bili puno bolji koristeći generator generaciju!

s = 'baacabcaab'
p = 'a'
def find_char (string, znak):
  za indeks, str_char u nabrajanju (niz):
    ako je str_char == znak:
      indeks prinosa
ispis (popis (find_char (s, p))) # [1, 2, 4, 7, 8]

Sad odmah možda nema očite koristi, ali idemo na moje prvo pitanje: "što ako želimo samo prvi rezultat; hoćemo li trebati napraviti potpuno novu funkciju? "

S funkcijom generatora ne trebamo prepisivati ​​toliko logike.
ispis (sljedeći (find_char (s, p))) # 1

Sada bismo mogli dohvatiti prvu vrijednost popisa koju je dalo naše originalno rješenje, ali na ovaj način dobivamo samo prvo podudaranje i zaustavljamo ponavljanje popisa. Generator će se tada odbaciti i neće se stvoriti ništa drugo; masovno štedi memoriju.

Zaključak

Ako ikada stvarate funkciju, nabraja vrijednosti na ovom popisu.

def foo (bar):
  vrijednosti = []
  za x u baru:
    # neka logika
    values.append (x)
  povratne vrijednosti

Razmislite o tome da vratite iterator s klasom, funkcijom generatora ili izrazom generatora na takav način:

def foo (bar):
  za x u baru:
    # neka logika
    prinos x

Resursi i izvori

PEPs

  • Generatori
  • Izrazi generatora PEP
  • Prinos od PEP

Članci i niti

  • iterators
  • Iterable vs Iterator
  • Dokumentacija generatora
  • Iteratori vs Generatori
  • Izraz generatora i funkcija
  • Recruzivni generatori

definicije

  • Iterable
  • iterator
  • Generator
  • Generator Iterator
  • Izraz generatora

Izvorno objavljeno na https://blog.dacio.dev/2019/05/03/python-iterators-and-generators/.