Fantastilised iteraatorid ja kuidas neid teha

Foto: John Matychuk saidil Unsplash

Probleem

Make Schoolis õppimise ajal olen näinud, kuidas mu eakaaslased kirjutavad funktsioone, mis loovad üksuste loendeid.

s = 'baacabcaab'
p = 'a'
def find_char (string, märk):
  indeksid = loend ()
  indeksi jaoks str_char loendis (string):
    kui str_char == märk:
      indeksid.append (register)
  tootlusindeksid
prindi (leidke_kõne (s, p)) # [1, 2, 4, 7, 8]

See juurutamine töötab, kuid sellega kaasnevad mõned probleemid:

  • Mis siis, kui me tahame ainult esimest tulemust; kas peame tegema täiesti uue funktsiooni?
  • Mis siis, kui kõik, mida me teeme, on tulemuse korra ületamine, kas peame iga elemendi mällu talletama?

Iteraatorid on nendele probleemidele ideaalne lahendus. Need toimivad nagu “laisad loendid”, selle asemel, et tagastada iga toodetud väärtusega loend ja tagastada iga element ükshaaval.

Iteraatorid tagastavad väärtused laiskalt; mälu kokkuhoid.

Mõelgem siis nende tundmaõppimisele!

Sisseehitatud iteraatorid

Kõige sagedamini esinevad iteraatorid on enumerate () ja zip (). Mõlemad need väärtused naasevad järgmiselt () koos nendega.

range () ei ole aga iteraator, vaid „laisalt korratav” - seletus

Saame teisendada vahemiku () iteraatoriks iteriga (), seega teeme selle õppimise huvides oma näidete jaoks.

my_iter = iter (vahemik (10))
print (järgmine (my_iter)) # 0
print (järgmine (my_iter)) nr 1

Iga järgmise () kõne korral saame järgmise vahemiku väärtuse; on mõtet eks? Kui soovite iteraatori selle loendiks teisendada, annate sellele lihtsalt loendi koostaja.

my_iter = iter (vahemik (10))
print (loend (my_iter)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Sellist käitumist jäljendades hakkame mõistma iteraatorite toimimist paremini.

my_iter = iter (vahemik (10))
my_list = list ()
proovige:
  samas tõsi:
    my_list.append (järgmine (my_iter))
välja arvatud StopIteration:
  üle andma
print (my_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Näete, et meil oli vaja see märata prooviavaldusesse. Seda seetõttu, et iteraatorid tõstavad StopIterationi, kui nad on ammendatud.

Nii et kui helistame järgmisele oma ammendatud vahemiku iteraatorile, saame selle vea.

järgmine (my_iter) # Tõstab: StopIteration

Iteraatori tegemine

Proovime teha iteraatori, mis käitub vahemikus ainult stoppargumendiga, kasutades kolme tavalist iteraatoritüüpi: klassid, generaatori funktsioonid (saagis) ja generaatori avaldised

Klass

Iteraatori loomise vana viis oli selgesõnaliselt määratletud klass. Selleks, et objekt oleks iteraator, peab ta rakendama __iter __ (), mis tagastab ennast, ja __next __ (), mis tagastab järgmise väärtuse.

klass my_range:
  _vool = -1
  def __init __ (ise, peatu):
    self._stop = peatus
  def __iter __ (ise):
    tagasi ise
  def __ järgmine __ (ise):
    ise.voolu + = 1
    kui ise.vool> = ise.voolu:
      tõsta StopIteration
    naase ise
r = my_range (10)
print (loend (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

See polnud liiga raske, kuid kahjuks peame järgmise () kõnede vahel muutujaid jälgima. Mulle isiklikult ei meeldi katlaplaat ega see, kuidas ma mõtlen silmuste muutmisele, kuna see pole ripplahendus, seega eelistan generaatoreid

Peamine eelis on see, et saame lisada täiendavaid funktsioone, mis muudavad selle sisemisi muutujaid, näiteks _stop, või luua uusi iteraatoreid.

Klasside iteraatoritel on vaja katelplaati vaja, aga neil võib olla ka lisafunktsioone, mis muudavad olekut.

Generaatorid

PEP 255 tutvustas tootluse märksõna abil lihtsaid generaatoreid.

Tänapäeval on generaatorid iteraatorid, mida on lihtsalt lihtsam teha kui nende klassi kolleegidega.

Generaatori funktsioon

Generaatori funktsioonid on see, mida selles PEP-s lõpuks arutati ja on minu lemmik iteraatori tüüp, nii et alustame sellest.

def my_range (peatus):
  indeks = 0
  samas indeks 
r = my_range (10)
print (loend (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Kas näete, kui ilusad need 4 koodirida on? Selle tipptasemel on see pisut lühem kui meie loendi rakendamine!

Generaator töötab iteraatoritega, mille katel on väiksem kui tavalise loogikavooluga klassides.

Generaatori funktsioonid peatavad automaatselt täitmise ja tagastavad määratud väärtuse iga järgmise kõnega (). See tähendab, et ühtegi koodi ei käivitata enne esimest järgmist () kõnet.

See tähendab, et voog on selline:

  1. järgmine () kutsutakse,
  2. Kood täidetakse kuni järgmise tootlusavalduseni.
  3. Saagikus paremal olev väärtus tagastatakse.
  4. Täitmine on peatatud.
  5. 1–5 korrake iga järgmise () kõne puhul, kuni viimane koodirida on käes.
  6. StopIteration on tõstatatud.

Generaatori funktsioonid võimaldavad teil kasutada ka märksõna saagikust, mis future next () kutsub teisele itereeritavale, kuni nimetatud iteratsioon on ammendatud.

def yielded_range ():
  saagis my_range (10)
print (loend (yielded_range ())) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

See polnud eriti keeruline näide. Kuid saate seda teha ka rekursiivselt!

def my_range_recursive (peatus, praegune = 0):
  kui praegune> = stopp:
    tagasi
  saagisevool
  saagis my_range_recursive (stopp, praegune + 1)
r = my_range_recursive (10)
print (loend (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Generaatori väljend

Generaatori avaldised võimaldavad meil luua iteraatoreid üherealisena ja need on head, kui me ei pea sellele väliseid funktsioone andma. Kahjuks ei saa me avaldist kasutades teist my_range'i luua, kuid saame töötada selle korratavate toimingutega nagu meie viimane funktsioon my_range.

my_doubled_range_10 = (x * 2 x jaoks my_range'is (10))
print (loend (my_doubled_range_10)) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Lahe on selle juures see, et ta teeb järgmist:

  1. Loend küsib my_doubled_range_10 järgmise väärtuse.
  2. my_doubled_range_10 küsib my_range'ilt järgmise väärtuse.
  3. my_doubled_range_10 tagastab my_range väärtuse, mis on korrutatud 2-ga.
  4. Loend lisab väärtuse iseendale.
  5. 1–5 korda kuni my_doubled_range_10 tõstab StopIterationi, mis juhtub siis, kui my_range teeb.
  6. Tagastatakse loend, mis sisaldab kõiki väärtusi, mille tagastab my_doubled_range.

Filtrimist saame teha isegi generaatorlausete abil!

my_even_range_10 = (x x jaoks my_range (10), kui x% 2 == 0)
print (loend (my_even_range_10)) # [0, 2, 4, 6, 8]

See on väga sarnane eelmisega, välja arvatud juhul, kui my_even_range_10 tagastab ainult need väärtused, mis vastavad antud tingimusele, seega ainult väärtused vahemikus [0, 10).

Selle kõige käigus loome ainult nimekirja, sest me käskisime seda.

Kasu

Allikas

Kuna generaatorid on iteraatorid, on iteraatorid iteraatorid ja iteraatorid tagastavad väärtused laiskalt. See tähendab, et neid teadmisi kasutades saame luua objekte, mis annavad meile objekte ainult siis, kui me neid palume ja vaatamata paljudele, mis meile meeldivad.

See tähendab, et saame generaatorid suunata funktsioonidesse, mis üksteist vähendavad.

print (summa (my_range (10))) # 45

Summa sel viisil arvutades välditakse loendi loomist, kui me lihtsalt teeme selle kokku ja siis visame ära.

Võime esimese näite ümber kirjutada generaatori funktsiooni abil palju paremaks!

s = 'baacabcaab'
p = 'a'
def find_char (string, märk):
  indeksi jaoks str_char loendis (string):
    kui str_char == märk:
      tootlusindeks
prindi (loend (leidke_kõne (s, p)))) # [1, 2, 4, 7, 8]

Nüüd ei pruugi ilmselget kasu olla, kuid lähme minu esimese küsimuse juurde: “mis siis, kui me tahame ainult esimest tulemust; kas peame tegema täiesti uue funktsiooni? ”

Generaatori funktsiooni abil ei pea me nii palju loogikat ümber kirjutama.
prindi (järgmine (leidke_hari (s, p))) # 1

Nüüd võisime hankida loendi esimese väärtuse, mille meie algne lahendus andis, kuid sel viisil saame alles esimese vaste ja lõpetame itereerimise kogu loendi kohal. Seejärel generaator visatakse ära ja midagi muud ei looda; massiliselt mälu säästmine.

Järeldus

Kui loote kunagi funktsiooni, kogunevad väärtused sellisesse loendisse.

def foo (baar):
  väärtused = []
  x jaoks riba:
    # mingi loogika
    value.append (x)
  tagasta väärtused

Kaaluge selle tagastamist iteraatori koos klassi, generaatori funktsiooni või generaatori avaldisega näiteks:

def foo (baar):
  x jaoks riba:
    # mingi loogika
    saagis x

Ressursid ja allikad

PEP-d

  • Generaatorid
  • Generaatorlaused PEP
  • Saagis PEP-st

Artiklid ja niidid

  • Iteraatorid
  • Mõttetu vs iteraator
  • Generaatori dokumentatsioon
  • Iteraatorid vs generaatorid
  • Generaatori avaldis vs funktsioon
  • Rekuperatiivgeneraatorid

Mõisted

  • Märkimisväärne
  • Iteraator
  • Generaator
  • Generaatori iteraator
  • Generaatori väljend

Algselt avaldati aadressil https://blog.dacio.dev/2019/05/03/python-iterators-and-generators/.