Adatvesztést nem okozó takewhile-szerű iterátor előállítása

A szabványos könyvtár itertools moduljának takewhile() iterátora a második argumentumként megadott sorozat elemeit mindaddig kiadja, amíg az első argumentumként átadott függvény az elemekre meghívva True értéket eredményez. A takewhile() használatakor azonban megfontoltnak kell lenni; át kell gondolni, hogy az adott helyzetben alkalmazható-e. Ugyanis, amennyiben a sorozatelemeket szolgáltató argumentum egy iterátor, az az elem, amely először nem felel meg a feltételnek, kikerül a bemeneti sorozatból, és később már nem érhető el. Ez gondot okozhat, ha a takewhile() futása után a maradék sorozatelemeket is fel kell dolgozni.

Ha tehát a feladat olyan, hogy egy sorozatot két részre kell bontani egy feltétel alapján és utána mindkét sorozatrészt fel kell dolgozni, akkor ennek megvalósítására a takewhile() nem alkalmazható. Ezért készítsünk magunknak egy olyan iterátort, amely ezt az igényt teljesíteni tudja.

A probléma ugyebár az, hogy ahhoz, hogy eldöntsük, hogy az aktuális elemre a feltétel teljesül, azt ki kell kérni a sorozatértékeket szolgáltató iterátorból. Ha a feltétel nem teljesül, akkor ezt az elemet nem adjuk ki. Viszont ezt valamilyen módon meg kell őrizni ahhoz, hogy ezen értéket is magába foglaló maradék sorozat teljes egészében rendelkezésre álljon.

Mint általában a legtöbb feladatot, ezt is többféle módon lehet megoldani. A most alkalmazott megvalósítás elve a következő:

  • A sorozatelemeket szolgáltató iterálható objektumból két független iterátort készítünk az itertools tee iterátorát használva.
  • Az első iterátorból sorban kikérjük az értékeket.
  • Ha a leállási feltételt meghatározó predikátum függvény az aktuális sorozatelemre True értékű, akkor ezt az értéket kiadjuk, a második iterátorból pedig eltávolítjuk.
  • Ha a predikátum függvény False értéket ad vissza, vagyis innentől le kell állni az elemkiadással, akkor egy StopIteration kivételt keltünk.
  • Ezt a kivételt lekezeljük úgy, hogy az objektumunk remainder_iterator attribútumához a második iterátort rendeljük (ami a maradék elemeket tudja majd kiadni), és aztán visszatérünk a hívó kódba.
  • Ha az első iterátor kiürül, akkor automatikusan egy StopIteration kivétel fog keletkezni, amit az előbbiek szerint, ugyanúgy kezelünk le.

Az igényeinket kielégítő és az előbbi elveknek megfelelő iterátort mind generátorfüggvénnyel, mind iterálható osztálypéldánnyal meg tudjuk valósítani. Ezek definíciói alább láthatók. A kommentek segítik a működés megértését.

A tesztsorok pedig következők.

Bár az itertools modulban található tee iterátor alkalmazása nem túl gyakori, de most egy olyan példát mutattunk, ahol jól tudtuk használni. Ehhez kapcsolódóan megjegyezzük, hogy a tee használata olyan esetekben hatékony, ahol az általa létrehozott iterátorokból felváltva kérjük ki az elemeket, nagyjából egyenlő kis adagokban. Ellenkező esetben az átmeneti memóriahasználat nagy lehet. Ilyenkor egy lista alkalmazása egyszerűbb és gyorsabb futást eredményez.

E bejegyzésben elsődlegesen az iterátorok, generátorfüggvény és iterátor protokoll, valamint az itertools modul takewhile és tee iterátorainak alkalmazása volt a középpontban. Ezekkel a Python tudásépítés lépésről lépésre című e-könyv „Kifogyhatatlan sorozatlövők – generátorfüggvények” és „Mik azok a protokollok?” fejezetek, továbbá a „Mágikus metódusok egyedi igényre szabott osztályokban” fejezet „Iterátorok és iterálható objektumok megvalósítása” alfejezete, valamint a „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezet „Speciális iterátorok” alfejezete foglalkozik részletesen.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.