Fantastični iteratorji in kako jih narediti

Fotografija Johna Matychka na Unsplash

Težava

Med učenjem v šoli Make sem videl, da moji vrstniki pišejo funkcije, ki ustvarjajo sezname predmetov.

s = 'baacabcaab'
p = 'a'
def find_char (niz, znak):
  indeksi = seznam ()
  za indeks, str_char v naštevanju (niz):
    če je str_char == znak:
      index.append (indeks)
  indeksi donosnosti
natisni (find_char (s, p)) # [1, 2, 4, 7, 8]

Ta izvedba deluje, vendar predstavlja nekaj težav:

  • Kaj pa, če želimo le prvi rezultat; bomo morali narediti povsem novo funkcijo?
  • Kaj, če vse, kar storimo, enkrat prekrivamo rezultat, ali moramo vsak element shraniti v pomnilnik?

Iteratorji so idealna rešitev za te težave. Delujejo kot "leni seznami", ker namesto da vrnejo seznam z vsako vrednostjo, ki jo ustvari, in vrne vsak element posebej.

Iteratorji leno vračajo vrednosti; varčevanje spomina.

Torej, pojdimo v učenje o njih!

Vgrajeni iteratorji

Najpogosteje iteratorji so oštevilčenje () in zip (). Obe leno vrneta vrednosti, ko ju naslednjič () dobita.

vendar obseg () ni iterator, ampak "leno ponovljiv." - Pojasnilo

Z iter () lahko pretvorimo range () v iterator, zato bomo to storili za naše primere zaradi učenja.

my_iter = iter (obseg (10))
natisni (naslednji (moj_iter)) # 0
tisk (naslednji (moj_iter)) # 1

Ob vsakem klicu next () dobimo naslednjo vrednost v našem razponu; ima smisel? Če želite iterator pretvoriti v seznam, mu preprosto dodajte konstruktor seznama.

my_iter = iter (obseg (10))
natisni (seznam (moj_iter)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Če oponašamo takšno vedenje, bomo začeli bolj razumeti, kako delujejo iteratorji.

my_iter = iter (obseg (10))
my_list = seznam ()
poskusi:
  medtem ko je resnično:
    my_list.append (naslednji (my_iter))
razen StopIteration:
  prehod
natisni (moj seznam) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Vidite, da smo ga morali zaviti v izjavo o poskusu ulova. To je zato, ker iteratorji dvignejo StopIteration, ko so izčrpani.

Če bomo naslednjič poklicali naš izčrpani iterator obsega, bomo dobili to napako.

naslednji (my_iter) # Dvig: StopIteration

Izdelava iteratorja

Poskusimo narediti iterator, ki se obnaša kot obseg samo z argumentom zaustavitve s pomočjo treh običajnih vrst iteratorjev: razredi, funkcije generatorja (donos) in izrazi generatorja

Razred

Stari način ustvarjanja iteratorja je potekal skozi izrecno določen razred. Da je predmet iterator, mora implementirati __iter __ (), ki se vrne, in __next __ (), ki vrne naslednjo vrednost.

razred my_range:
  _current = -1
  def __init __ (samostojno, ustavi):
    samo._stop = postanek
  def __iter __ (samo):
    vrni se jaz
  def __slednji __ (samo):
    self._current + = 1
    če je self._current> = self._stop:
      dvigni StopIteration
    vrnite se._ trenutni
r = my_range (10)
natisni (seznam (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

To ni bilo pretežko, a na žalost moramo spremljati spremenljivke med klici next (). Osebno mi ni všeč kotlovna plošča ali spreminjanje načina razmišljanja o zankah, ker to ni rešitev, ki se spušča, zato imam raje generatorje

Glavna prednost je, da lahko dodamo dodatne funkcije, ki spreminjajo njegove notranje spremenljivke, kot je _stop ali ustvarjajo nove iteratorje.

Iteratorji razredov so slabo od potrebe po kotlovski plošči, lahko pa imajo dodatne funkcije, ki spreminjajo stanje.

Generatorji

PEP 255 je z uporabo ključne besede donos uvedel "enostavne generatorje".

Danes so generatorji iteratorji, ki jih je preprosto lažje izdelati kot njihovi kolegi iz razreda.

Funkcija generatorja

Funkcije generatorja so tisto, o čemer se je na koncu razpravljalo v tem PEP-u in so moja najljubša vrsta iteratorja, zato začnimo s tem.

def my_range (stop):
  indeks = 0
  medtem ko indeks 
r = my_range (10)
natisni (seznam (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ali vidite, kako lepi so ti 4 vrstici kode? Je nekoliko bistveno krajši od izvajanja našega seznama in ga nadgradimo!

Generator deluje iteratorje z manj kotlovne plošče kot razredi z normalnim logičnim tokom.

Generator samodejno "zaustavi" izvedbo in vrne določeno vrednost z vsakim klicom next (). To pomeni, da se do prvega naslednjega () klica ne zažene nobena koda.

To pomeni, da je pretok takšen:

  1. naslednji () se pokliče,
  2. Koda se izvede do naslednje izjave o donosu.
  3. Vrača se vrednost na desni donosnosti.
  4. Izvajanje je zaustavljeno.
  5. 1–5 ponovite za vsak naslednji () klic, dokler se ne zadnja zadnja vrstica kode.
  6. StopIteracija je dvignjena.

Funkcije generatorja omogočajo tudi, da uporabite donos iz ključne besede, ki ga prihodnji () pokliče na drugega, ki ga je mogoče prebaviti, dokler omenjena iterable ni izčrpana.

def yielded_range ():
  donos iz my_range (10)
natisni (seznam (dobite_razen ())) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

To ni bil posebej zapleten primer. Lahko pa to storite celo rekurzivno!

def my_range_recursive (stop, tok = 0):
  če je tok> = stop:
    vrnitev
  dovodni tok
  izkoristek my_range_recursive (stop, tok + 1)
r = my_range_recursive (10)
natisni (seznam (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Izraz generatorja

Izrazi generatorja nam omogočajo, da ustvarimo iteratorje kot enojno linijo in so dobri, kadar nam ni treba dodeljevati zunanjih funkcij. Na žalost ne moremo narediti drugega my_rangea z izrazom, lahko pa delujemo na iterables, kot je naša zadnja funkcija my_range.

my_doubled_range_10 = (x * 2 za x v my_range (10))
tisk (seznam (my_doubled_range_10)) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Kul pri tem je, da naredi naslednje:

  1. Seznam vpraša my_doubled_range_10 za naslednjo vrednost.
  2. my_doubled_range_10 vpraša my_range za naslednjo vrednost.
  3. my_doubled_range_10 vrne vrednost my_range, pomnoženo z 2.
  4. Seznam doda vrednost k sebi.
  5. Ponovite 1–5, dokler my_doubled_range_10 ne dvigne StopIteration, ki se zgodi, ko my_range.
  6. Seznam se vrne z vsako vrednostjo, ki jo vrne my_doubled_range.

Tudi filtriranje lahko naredimo s pomočjo izrazov generatorja!

my_even_range_10 = (x za x v my_range (10), če x% 2 == 0)
natisni (seznam (my_even_range_10)) # [0, 2, 4, 6, 8]

To je zelo podobno prejšnji, le da my_even_range_10 vrne samo vrednosti, ki ustrezajo dano pogoj, tako da so v območju le 0, celo vrednosti.

Skozi vse to ustvarjamo le seznam, ker smo mu povedali.

Ugodnost

Vir

Ker so generatorji iteratorji, so iteratorji iterable, iteratorji pa leno vračajo vrednosti. To pomeni, da lahko s pomočjo tega znanja ustvarimo predmete, ki nam bodo dajali predmete le, ko jih bomo prosili in ne glede na to, koliko jih imamo radi.

To pomeni, da lahko posredujemo generatorje v funkcije, ki se med seboj zmanjšujejo.

tisk (vsota (my_range (10))) # 45

Če izračunamo vsoto na ta način, se izognemo ustvarjanju seznama, ko vse, kar počnemo, seštevamo in nato zavržemo.

Prvi primer lahko na novo napišemo, da je veliko boljši s pomočjo funkcije generatorja!

s = 'baacabcaab'
p = 'a'
def find_char (niz, znak):
  za indeks, str_char v naštevanju (niz):
    če je str_char == znak:
      indeks donosa
natisni (seznam (find_char (s, p))) # [1, 2, 4, 7, 8]

Takoj morda ne bo očitne koristi, ampak pojdimo na moje prvo vprašanje: "kaj pa če želimo le prvi rezultat; bomo morali narediti povsem novo funkcijo? "

S funkcijo generatorja nam ni treba prepisovati toliko logike.
tisk (naslednja (find_char (s, p))) # 1

Zdaj bi lahko prišli do prve vrednosti seznama, ki jo je dala naša izvirna rešitev, vendar na ta način dobimo samo prvo ujemanje in nehamo ponavljati seznam. Nato se generator zavrže in ne nastane nič drugega; množično varčuje spomin.

Zaključek

Če boste kdaj ustvarili funkcijo, nabira vrednosti na tem seznamu.

def foo (bar):
  vrednosti = []
  za x v baru:
    # nekaj logike
    vrednosti.append (x)
  povratne vrednosti

Razmislite o tem, da bi vrnili iterator z razredom, funkcijo generatorja ali izražanjem generatorja, kot je:

def foo (bar):
  za x v baru:
    # nekaj logike
    donos x

Viri in viri

PEPs

  • Generatorji
  • Izrazi generatorja PEP
  • Dobitek iz PEP

Članki in niti

  • Iteratorji
  • Iterable vs Iterator
  • Dokumentacija generatorja
  • Iteratorji proti generatorjem
  • Izražanje generatorja in funkcija
  • Recrusive generatorji

Opredelitve

  • Iterable
  • Iterator
  • Generator
  • Generator Iterator
  • Izraz generatorja

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