Fantasztikus idézők és hogyan lehet őket elkészíteni

Fotó: John Matychuk az Unsplash-en

A probléma

A Make Schoolban tanulva láttam, hogy társaik olyan funkciókat írnak, amelyek cikkeket készítenek.

s = 'baacabcaab'
p = 'a'
def find_char (karakterlánc, karakter):
  indexek = lista ()
  az indexhez: str_char inumerate (string):
    ha str_char == karakter:
      indices.append (index)
  visszatérési indexek
nyomtatás (find_char (s, p)) # [1, 2, 4, 7, 8]

Ez a megvalósítás működik, de néhány problémát vet fel:

  • Mi lenne, ha csak az első eredményt akarjuk; szükségünk lesz egy teljesen új funkcióra?
  • Mi lenne, ha csak az eredmény egyszeri áthúzását tennénk, minden elemet meg kell tárolnunk a memóriában?

Az iterátorok ideális megoldás ezekre a problémákra. Úgy működnek, mint a „lusta listák” abban a tekintetben, hogy ahelyett, hogy visszaadnának egy listát minden előállított értékkel, és minden elemet egyenként adnának vissza.

Az Iteratorok lazán visszatérnek az értékekre; memória megtakarítása.

Tehát merüljünk el megismerni őket!

Beépített iterátorok

Az iterátorok, amelyek leggyakrabban az enumerate () és a zip (). Mindkét lusta módon visszatér az érték a következővel () velük.

A range () azonban nem iterátor, hanem „lusta iterábilis”. - Magyarázat

Konvertálhatjuk a range () iterává iter () segítségével, így ezt megtesszük a példákra a tanulás kedvéért.

my_iter = iter (tartomány (10))
nyomtatás (következő (my_iter)) # 0
nyomtatás (következő (my_iter)) # 1

A következő () minden hívásakor megkapjuk a tartományunk következő értékét; van értelme igaz? Ha egy iterátort egy listává akar konvertálni, akkor csak adja meg a lista-kivitelezőt.

my_iter = iter (tartomány (10))
nyomtatás (lista (my_iter)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ha utánozzuk ezt a viselkedést, akkor jobban megismerjük az iteratorok működését.

my_iter = iter (tartomány (10))
my_list = list ()
próbálja meg:
  míg igaz:
    my_list.append (következő (my_iter))
kivéve a StopIteration:
  elhalad
print (my_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Láthatja, hogy be kellett csomagolnunk egy próbálkozási nyilatkozatba. Ennek oka az, hogy az iteratorok kimerítik a StopIteration-t.

Tehát, ha legközelebb felhívjuk a kimerült tartomány iterátort, akkor megkapjuk ezt a hibát.

következő (my_iter) # Emel: StopIteration

Iterator készítése

Próbáljunk meg olyan iterátort elkészíteni, amely tartományhoz hasonlóan működik, csak a stop argumentummal, három gyakori iteratortípus használatával: Osztályok, Generátorfunkciók (hozam) és Generátor kifejezések

Osztály

Az iterátor létrehozásának régi módja egy kifejezetten meghatározott osztályon keresztül történt. Ahhoz, hogy egy objektum iterator legyen, az __iter __ () -ot kell végrehajtania, amely visszatér, és __next __ () -ot, amely a következő értéket adja vissza.

osztály my_range:
  _áram = -1
  def __init __ (önálló, stop):
    self._stop = stop
  def __iter __ (önálló):
    visszatérni önmagamhoz
  def __következő __ (önálló):
    saját.áram + = 1
    ha self._current> = self._stop:
      emelje meg a StopIteration-t
    visszatérő önáram
r = my_range (10)
nyomtatás (lista (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ez nem volt túl nehéz, de sajnos nyomon kell követnünk a változókat a következő () hívások között. Személy szerint nem szeretem a kazánlemezt vagy azt, hogy megváltoztassam a hurkokra gondolkodó gondolkodásmódot, mert ez nem egy drop-in megoldás, ezért inkább a generátorokat szeretem

A fő előnye az, hogy hozzáadhatunk további funkciókat, amelyek módosítják a belső változókat, például a _stop, vagy új iterátorokat hozhat létre.

Az osztály iterátorok hátránya, hogy szükségük van a kazánlapra, azonban rendelkezhetnek további funkciókkal, amelyek módosítják az állapotot.

Generátor

A PEP 255 bevezette az „egyszerű generátorokat” a hozam kulcsszó segítségével.

Manapság a generátorok iterátorok, amelyeket egyszerűbb elkészíteni, mint az osztályaik.

Generátor funkció

A generátor funkciók azok, amelyeket végül megvitattak abban a PEP-ben, és ezek a kedvenc iterator típusom, tehát kezdjük ezzel.

def my_range (stop):
  index = 0
  míg index 
r = my_range (10)
nyomtatás (lista (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Látod, milyen szép ez a 4 sor sor? Kissé lényegesen rövidebb, mint a listánk végrehajtásánál, hogy felülmúljuk!

A generátor kevesebb kazánlapú iterátorokat működtet, mint a normál logikai áramlású osztályok.

A generátor funkciók automatikusan „szüneteltetik” a végrehajtást és visszatérnek a megadott értékre a következő () hívásokkal. Ez azt jelenti, hogy egyetlen kódot sem futtatunk az első következő () hívásig.

Ez azt jelenti, hogy az áramlás ilyen:

  1. a következő () nevű,
  2. A kód a következő hozamnyilatkozatig kerül végrehajtásra.
  3. A hozam jobb oldalán lévő érték visszatér.
  4. A végrehajtás szünetel.
  5. 1–5 ismételje meg minden következő () hívást, amíg el nem éri a kód utolsó sorát.
  6. A StopIteration emelt.

A generátor funkciók azt is lehetővé teszik, hogy a kulcsszóból származó hozamot felhasználhassa, amely a jövő következő () hív egy másik iterábilisra, amíg az említett itereerálhatóság kimerül.

def yielded_range ():
  hozam a my_range-ból (10)
print (lista (yielded_range ())) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ez nem volt különösebben összetett példa. De még rekurzívan is megteheti!

def my_range_recursive (stop, jelenlegi = 0):
  ha aktuális> = stop:
    Visszatérés
  hozamáram
  hozam a my_range_recursive-ből (stop, jelenlegi + 1)
r = my_range_recursive (10)
nyomtatás (lista (r)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Generátor kifejezés

A generátor kifejezések lehetővé teszik, hogy iterátorokat hozzunk létre egysorosként, és akkor jók, ha nem kell külső funkciókat adnunk neki. Sajnos nem állíthatunk elő egy új my_range kifejezést egy kifejezés használatával, de úgy dolgozhatunk az iterables-en, mint az utolsó my_range függvényünk.

my_doubled_range_10 = (x * 2 x esetén a my_range-ban (10))
nyomtatás (lista (my_doubled_range_10)) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

A remek dolog az, hogy a következőket teszi:

  1. A lista kéri a my_doubled_range_10 következő értékét.
  2. my_doubled_range_10 kéri a my_range következő értékét.
  3. my_doubled_range_10 a my_range értékét szorozva 2-vel adja vissza.
  4. A lista hozzáadja az értéket magának.
  5. 1–5 ismétlés, amíg a my_doubled_range_10 felveszi a StopIteration műveletet, ami akkor történik, amikor a my_range nem.
  6. A lista visszatér a my_doubled_range által visszaadott minden értéket tartalmazó értékkel.

Még szűrhetünk generátor kifejezésekkel is!

my_even_range_10 = (x x-ra a my_range-ban (10), ha x% 2 == 0)
nyomtatás (lista (my_even_range_10)) # [0, 2, 4, 6, 8]

Ez nagyon hasonlít az előzőhöz, kivéve, hogy a my_even_range_10 csak azokat az értékeket adja vissza, amelyek megfelelnek az adott feltételeknek, tehát csak a [0, 10) tartomány közötti egyenlő értékek.

Mindezek során csak azért hozunk létre egy listát, mert elmondtuk neki.

Az előny

Forrás

Mivel a generátorok iterátorok, az iteratorok iterablek, és az iteratorok lazán visszatérnek az értékekre. Ez azt jelenti, hogy ezen ismeretek felhasználásával olyan tárgyakat hozhatunk létre, amelyek tárgyakat csak akkor adnak nekünk, amikor kérjük őket, és bármennyit is szeretünk.

Ez azt jelenti, hogy átadhatjuk a generátorokat olyan funkcióknak, amelyek csökkentik egymást.

nyomtatás (összeg (my_range (10))) # 45

Az összeg ilyen módon történő kiszámításával elkerülhető egy lista létrehozása, amikor csak összeadjuk őket, majd eldobjuk.

Az első példát átírhatjuk úgy, hogy sokkal jobb legyen egy generátor funkció segítségével!

s = 'baacabcaab'
p = 'a'
def find_char (karakterlánc, karakter):
  az indexhez: str_char inumerate (string):
    ha str_char == karakter:
      hozam index
nyomtatás (lista (find_char (s, p)))) # [1, 2, 4, 7, 8]

Most azonnal lehet, hogy nincs nyilvánvaló előnye, de térjünk fel az első kérdésemhez: „Mi lenne, ha csak az első eredményt akarjuk; szükségünk lesz egy teljesen új funkcióra? ”

A generátor funkcióval nem kell annyi logikát átírnunk.
nyomtatás (következő (find_char (s, p)))) # 1

Most visszakereshetjük a lista első értékét, amelyet eredeti megoldásunk adott, de így csak az első mérkőzést kapjuk meg, és abbahagyjuk a lista feletti iterálást. A generátort ezután eldobják, és semmi mást nem hoznak létre; tömegesen takarít meg memóriát.

Következtetés

Ha valaha is létrehoz egy funkciót, akkor az összegyűjtött értékek egy ilyen listában jelennek meg.

def foo (bár):
  értékek = []
  x-re a sávon:
    # némi logika
    values.append (x)
  visszatérési értékek

Fontolja meg, ha visszaad egy iterátort osztály, generátor függvény vagy generátor kifejezéssel, például:

def foo (bár):
  x-re a sávon:
    # némi logika
    hozam x

Források és források

PEP

  • Generátor
  • Generátor kifejezések PEP
  • Hozam PEP-ből

Cikkek és szálak

  • iterátorokat
  • Iterer vs Iterator
  • Generátorok dokumentációja
  • Iteratorok vs generátorok
  • Generátor kifejezés vs funkció
  • Recrukciós generátorok

Definíciók

  • Iterable
  • iterátor
  • Generátor
  • Generátor Iterator
  • Generátor kifejezés

Eredetileg a https://blog.dacio.dev/2019/05/03/python-iterators-and-generators/ oldalon tették közzé.