Fantastiski atkārtotāji un kā tos padarīt

Jāņa Matjukoka foto vietnē Unsplash

Problēma

Mācoties Make School, esmu redzējis, kā mani vienaudži raksta funkcijas, kas veido priekšmetu sarakstus.

s = 'baacabcaab'
p = 'a'
def find_char (virkne, raksturs):
  indeksi = saraksts ()
  indeksam - str_char uzskaitījumā (virkne):
    ja str_char == raksturs:
      indeksi.append (indekss)
  atgriešanās indeksi
print (find_char (s, p)) # [1, 2, 4, 7, 8]

Šī ieviešana darbojas, taču tā rada dažas problēmas:

  • Ko darīt, ja mēs vēlamies tikai pirmo rezultātu; vai mums būs jāveic pilnīgi jauna funkcija?
  • Ko darīt, ja tikai mēs vienreiz atkārtojam rezultātu, vai katrs elements ir jāsaglabā atmiņā?

Iteratori ir ideāls risinājums šīm problēmām. Tie darbojas kā “slinki saraksti”, tā vietā, lai atgrieztos sarakstu ar katru vērtību, ko tas rada, un katru elementu atdod pa vienam.

Iteratori slinki atdod vērtības; ietaupot atmiņu.

Tāpēc ienirsim to apguvē!

Iebūvētie atkārtotāji

Iteratori, kas visbiežāk ir enumerate () un zip (). Abas šīs vērtības slinki atgriež ar nākamo ().

diapazons () tomēr nav iterators, bet gan “slinks iterējams”. - Paskaidrojums

Mēs varam pārveidot diapazonu () iterātā ar iter (), tāpēc mācīšanās labad to darīsim mūsu piemēriem.

my_iter = iter (diapazons (10))
print (nākamais (my_iter)) # 0
print (nākamais (my_iter)) # 1

Pēc katra nākamā () zvana mēs iegūstam nākamo vērtību mūsu diapazonā; ir jēga pareizi? Ja vēlaties pārveidot iteratoru sarakstā, jūs vienkārši piešķirat tam saraksta veidotāju.

my_iter = iter (diapazons (10))
drukāt (saraksts (my_iter)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ja mēs atdarināsim šo uzvedību, mēs sāksim saprast vairāk par iteratoru darbību.

my_iter = iter (diapazons (10))
my_list = saraksts ()
izmēģināt:
  kamēr taisnība:
    my_list.append (nākamais (my_iter))
izņemot StopIteration:
  caurlaide
print (my_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Var redzēt, ka mums tas bija jāietver paziņojumā par izmēģinājumu. Tas ir tāpēc, ka iteratori palielina StopIteration, kad viņi ir izsmelti.

Tātad, ja mēs piezvanīsim nākamajam uz mūsu izsmeļošo diapazona iteratoru, mēs saņemsim šo kļūdu.

nākamais (my_iter) # Raizes: StopIteration

Iteratora izgatavošana

Mēģināsim izveidot iteratoru, kas darbojas kā diapazons ar tikai stop argumentu, izmantojot trīs izplatītus iteratoru tipus: klases, ģeneratora funkcijas (raža) un ģeneratoru izteiksmes

Klase

Iepriekšējais iteratora izveidošanas veids bija skaidri definēta klase. Lai objekts būtu iterators, tam jāīsteno __iter __ (), kas atdod sevi, un __next __ (), kas atdod nākamo vērtību.

klase my_range:
  pašreizējais = -1
  def __init __ (self, stop):
    self._stop = apstāties
  def __iter __ (self):
    atgriezt sevi
  def __ nākamais __ (pats):
    self._current + = 1
    ja self._current> = self._stop:
      paaugstināt StopIteration
    atgriezties Pašreizējā
r = my_range (10)
drukāt (saraksts (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Tas nebija pārāk grūti, bet diemžēl mums ir jāseko mainīgajiem lielumiem starp nākamā () zvaniem. Personīgi man nepatīk apkures katls vai tas, ka mainos, kā es domāju par cilpām, jo ​​tas nav nolaižams risinājums, tāpēc es dodu priekšroku ģeneratoriem

Galvenais ieguvums ir tas, ka mēs varam pievienot papildu funkcijas, kas modificē tā iekšējos mainīgos, piemēram, _stop, vai izveidot jaunus iteratorus.

Klases iteratoriem ir negatīvā puse, kas nepieciešama katla plāksnei, tomēr tiem var būt papildu funkcijas, kas maina stāvokli.

Ģeneratori

PEP 255 ieviesa “vienkāršus ģeneratorus”, izmantojot ienesīguma atslēgu.

Mūsdienās ģeneratori ir iteratori, kurus ir vienkārši vieglāk izgatavot nekā viņu klases kolēģus.

Ģeneratora funkcija

Ģeneratora funkcijas ir tas, kas galu galā tika apspriests tajā PEP, un ir mans iecienītākais iteratoru tips, tāpēc sāksim ar to.

def my_range (stop):
  indekss = 0
  kamēr indekss 
r = my_range (10)
drukāt (saraksts (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Vai redzat, cik skaistas ir šīs 4 koda līnijas? Tas ir nedaudz īsāks nekā mūsu saraksta ieviešana, lai to papildinātu!

Ģeneratoru funkcijas iteratori ar mazāku katlu plātni nekā klases ar normālu loģisko plūsmu.

Ģeneratora funkcijas automātiski “aptur” izpildi un atgriež norādīto vērtību ar katru nākamo zvanu (). Tas nozīmē, ka neviens kods netiek palaists līdz pirmajam nākamajam () zvanam.

Tas nozīmē, ka plūsma ir šāda:

  1. nākamais () tiek saukts,
  2. Kods tiek izpildīts līdz nākamajam ienesīguma paziņojumam.
  3. Tiek atgriezta vērtība ražas labajā pusē.
  4. Izpilde ir apturēta.
  5. 1–5 atkārtojiet katru nākamo () zvanu, līdz tiek nospiesta pēdējā koda rinda.
  6. StopIteration tiek paaugstināta.

Ģeneratora funkcijas ļauj arī izmantot ieguvumus no atslēgvārda, kurš nākotnes nākamais () izsauc uz citu atkārtojamu, līdz minētais atkārtojamais ir izsmelts.

def yielded_range ():
  raža no my_range (10)
drukāt (saraksts (yielded_range ())) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Tas nebija īpaši sarežģīts piemērs. Bet jūs pat varat to izdarīt rekursīvi!

def my_range_recursive (stop, current = 0):
  ja strāva> = apstāties:
    atgriezties
  ražas strāva
  raža no my_range_recursive (apstāšanās, pašreizējā + 1)
r = my_range_recursive (10)
drukāt (saraksts (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ģeneratora izteiksme

Ģeneratoru izteicieni ļauj mums izveidot iteratorus kā vienrindas, un tie ir labi, ja mums nav jāpiešķir tam ārējas funkcijas. Diemžēl mēs nevaram izveidot citu my_range, izmantojot izteiksmi, bet mēs varam strādāt pie tā atkārtojamiem parametriem, piemēram, mūsu pēdējā funkcija my_range.

my_doubled_range_10 = (x * 2 x manā diapazonā my_range (10))
print (list (my_doubled_range_10)) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Forši, ka tas notiek šādi:

  1. Sarakstā tiek lūgta my_doubled_range_10 nākamā vērtība.
  2. my_doubled_range_10 prasa my_range nākamo vērtību.
  3. my_doubled_range_10 atgriež my_range vērtību, kas reizināta ar 2.
  4. Saraksts piešķir vērtību sev.
  5. Atkārtojiet 1–5, līdz my_doubled_range_10 paaugstina StopIteration, kas notiek, kad my_range notiek.
  6. Tiek atgriezts saraksts ar katru vērtību, kuru atgriezusi my_doubled_range.

Mēs pat varam veikt filtrēšanu, izmantojot ģeneratora izteiksmes!

my_even_range_10 = (x x x my_range (10), ja x% 2 == 0)
drukāt (saraksts (my_even_range_10)) # [0, 2, 4, 6, 8]

Tas ir ļoti līdzīgs iepriekšējam, izņemot, ka my_even_range_10 atgriež tikai vērtības, kas atbilst dotajam nosacījumam, tāpēc tikai vērtības ir diapazonā no [0, 10).

Šajā visa starpā mēs izveidojam sarakstu tikai tāpēc, ka mēs to teicām.

Ieguvums

Avots

Tā kā ģeneratori ir iteratori, iteratori ir iteratori, un iteratori slinki atdod vērtības. Tas nozīmē, ka, izmantojot šīs zināšanas, mēs varam izveidot objektus, kas objektus mums piešķirs tikai tad, kad mēs tos prasīsim, un lai arī cik daudziem mums tas patiktu.

Tas nozīmē, ka mēs varam nodot ģeneratorus funkcijām, kas samazina viena otru.

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

Šādi aprēķinot summu, nav iespējams izveidot sarakstu, kad viss, ko mēs darām, ir to saskaitīšana un pēc tam izmešana.

Pirmo piemēru mēs varam pārrakstīt labāk, izmantojot ģeneratora funkciju!

s = 'baacabcaab'
p = 'a'
def find_char (virkne, raksturs):
  indeksam - str_char uzskaitījumā (virkne):
    ja str_char == raksturs:
      ienesīguma indekss
drukāt (saraksts (atrast_čaru (s, p))) # [1, 2, 4, 7, 8]

Tagad tūlīt, iespējams, nav acīmredzamu ieguvumu, bet pievērsīsimies manam pirmajam jautājumam: “kas būtu, ja mēs vēlamies tikai pirmo rezultātu; vai mums būs jāveic pilnīgi jauna funkcija? ”

Izmantojot ģeneratora funkciju, mums nav jāpārraksta tik daudz loģikas.
print (nākamais (find_char (s, p)))) # 1

Tagad mēs varētu iegūt pirmo saraksta vērtību, ko deva mūsu sākotnējais risinājums, bet šādā veidā mēs iegūstam tikai pirmo spēli un pārstājam atkārtot sarakstu. Pēc tam ģenerators tiks izmests, un nekas cits netiks izveidots; masveidā saglabājot atmiņu.

Secinājums

Ja jūs kādreiz veidojat funkciju, vērtības uzkrājas šādā sarakstā.

def foo (bārs):
  vērtības = []
  x joslā:
    # kāda loģika
    vērtības.append (x)
  atgriešanās vērtības

Apsveriet iespēju atgriezt iteratoru ar klases, ģeneratora funkciju vai ģeneratora izteiksmi, piemēram:

def foo (bārs):
  x joslā:
    # kāda loģika
    raža x

Resursi un avoti

PEP

  • Ģeneratori
  • Ģeneratoru izteiksmes PEP
  • Ienesīgums no PEP

Raksti un diegi

  • Iteratori
  • Iterable vs Iterator
  • Ģeneratora dokumentācija
  • Iteratori vs ģeneratori
  • Ģeneratora izteiksme vs funkcija
  • Recruzīvie ģeneratori

Definīcijas

  • Iteratīvs
  • Iterators
  • Ģenerators
  • Ģeneratora atkārtotājs
  • Ģeneratora izteiksme

Sākotnēji tas tika publicēts vietnē https://blog.dacio.dev/2019/05/03/python-iterators-and-generators/.