Hogyan definiáljunk saját sorozat típusú konténerobjektumot? – 1. rész

Az legutóbbi bejegyzésben sorozat típusú konténer elemeinek körkörös léptetéséről, más szóval forgatásáról volt szó, ahol több függvényt is készítettünk erre a célra. Ezek megvalósítása abban különbözött, hogy milyen előfeltételt kell, hogy teljesítsen az argumentumként átadott sorozat típusú konténer. Ilyen kritérium volt, hogy a sorozatobjektumra értelmezett-e a szeletképzés és az összefűzés művelete-e vagy sem, vagy hogy a sorozat egy példánya előállítható-e úgy, hogy a konstruktorba egy iterálható objektumot helyezünk, amelynek elemei lesznek a sorozatpéldány elemei.

Azt is mondtuk, hogy bár a beépített sorozattípusú konténerek (list, tuple, str, bytes, bytearray) mindegyikére értelmezett a szeletképzés és összefűzés, előfordulhat olyan sorozatkonténer, amely az előfeltételek közöl egyet vagy többet nem teljesít. Ebben a bejegyzésben ilyen sorozat típusú konténer egyénileg definiált osztályát fogunk előállítani, amely munkának elsődleges célja most nem a gyakorlati alkalmazhatóság, hanem annak bemutatása, hogy hogyan épül fel egy ilyen osztály, és mit és hogyan kell teljesítenie, hogy sorozat típusú konténerként viselkedjenek a példányai.

A létrehozáshoz annyi csak a kikötés, hogy a range kivételével nem használhatunk más beépített sorozat típusú konténert (ld fentebb felsorolva), mert akkor elég egyszerű lenne a dolgunk, holott épp az a lényeg, hogy azon gondolkodjunk el, hogy hogyan építsünk fel sorozat konténert meglévő sorozat konténer nélkül. /A range is kiváltható lenne, de ennyire nem bonyolítjuk a dolgot. /

A létrehozandó objektumunknak két alapjellemzővel kell rendelkezni a Python meghatározása szerint:

1) Konténer /vagy más szóhasználattal gyűjtemény (collection)/ kell, hogy legyen, és

2) teljesíteni kell a sorozatokra vonatkozó minimális jellemzőket.

Ahhoz, hogy konténer legyen egy objektum az kell, hogy

  • meg lehessen állapítani, hogy egy adott objektum elem-e vagy sem a konténernek, vagyis tartalmazásvizsgálatot lehet végezni az in és not in operátorokkal. Ehhez az osztályban definiálni kell a __contains__ metódust.
  • az elemszámát le lehessen kérdezni a len() függvénnyel, amihez az osztályban definiálni kell a __len__ metódust.
  • Iterálhatónak kell lenni. Ezt a követelményt vagy az __iter__ metódus definiálásával lehet kielégíteni, vagy ha indexelhető az objektum, akkor ez nem szükséges, elég, ha az indexelhetőséget biztosító __getitem__ metódust implementáljuk az osztályban.

A sorozatjellemzőkhöz biztosítani kell

  • az indexelhetőséget. Ezt, ahogy előbb említettük, a __getitem__ metódus definiálásával tehetjük meg. A szeletképzést is e metódus hatja végre, ha erre is fel akarjuk készíteni az osztályt, de ez nem kötelező kritérium.
  • a reversed() függvényt alkalmazva az objektumra az elemek fordított sorrendben előálljanak. Ehhez az osztályban definiálni kell a __reversed__ metódust. Bár egy sorozatot definiáló jellemzőket szűken értelmezve ez sem követelmény, de célszerű implementálni, különösen akkor, ha a szeletképzésre nem okosítottuk fel az osztályt. /Szeletképzéssel ugyanis a sorrendfordítás megvalósítható. /

Mindezeket tudva álljunk neki az osztálydefiníciónak.

A teljes kód az alábbi. Utána az egyes metódusok megvalósításához fűzünk egy kis magyarázatot.

Elsőként az __init__ függvényt írjuk meg. Ennek argumentuma egy iterálható objektum, amelynek elemeit sorrendben egy szótárban tároljuk el, amelyek kulcsai nullától kezdődő egész számok lesznek, amelyek a sorozat indexeiként fognak funkcionálni. Ez látjuk a képen az osztály első metódusaként.

A következő legyen a __contains__ metódus. Ennek visszatérési értéke a szótárértékekre elvégzett tartalmazásvizsgálat eredménye.

Most nézzük a __len__ metódust. Ez is nagyon egyszerű, mert csak a szótár elemszámát kell itt visszaadni.

Az indexelhetőséget megvalósító __getitem__ metódus egy kicsivel több megfontolást igényel. Az a könnyebb dolog, hogy ellenőrizzük, hogy az argumentum egész szám-e. Ha nem, akkor kivételt dobunk. Az viszont, hogy az index negatív szám is lehet körültekintést igényel minthogy a belső szótárunk kulcsai nemnegatív számok. Ezért először egy olyan kódot kell írni, amely a belső szótár hibamentes használatához nemnegatív indexértékeket biztosít akármilyen előjelű egész szám legyen is a metódus argumentuma. Ezt egy feltételes kifejezéssel érjük el, amely magát az argumentumot adja eredményül, ha a metódus argumenuma nemnegatív. Ellenkező esetben a sorozathossz és az argumentum összegét. Azért, mert mindig igaz, hogy egy sorozat nemnegatív indexeinek és negatív indexeinek a különbsége a sorozathosszat adja ki. Pl. egy nyolc elemből álló sorozat utolsó elemének indexe +7 vagy -1. Vagyis +7- (-1) = 8. Erről egy korábbi bejegyzésben ábrát is láthattunk.

Most, hogy már ismert a megfelelő nemnegatív index, ellenőrizzük, hogy ez benne van-e a belső szótár kulcsai között. Ha nem, akkor IndexError kivételt dobunk. Ha igen, érvényes indexről van szó, akkor a __getitem__ visszatérési értéke a belső szótár ezen indexhez mint kulcshoz tartozó értéke lesz.

A __reversed__ metódus egy iterátort kell, hogy visszaadjon, amely fordított sorrendben szolgáltatja ki a sorozat elemeit. Azzal, hogy már le tudjuk kérni a sorozatunk hosszát a len() függvénnyel, valamint indexszel a sorozat elemeit, e metódus kódja sem túl bonyolult, mert egy generátorkifejezésben, a sorozat maximális indexét ismerve, fordított sorrendben kérjük ki a sorozat értékeit.

Végül ahhoz, hogy értelmezhető formában lássuk az osztályunkból létrehozott sorozatpéldányokat, implementáljuk a __repr__ és __str__ metódusokat. Mindkét karakterlánc-reprezentációs metódus esetén a belső szótár értékeit vesszük ki sorban és alakítjuk karakterlánccá. A sorozatunk egyéni voltát kihangsúlyozandó, a __str__ esetén az elemeket egy speciális formájú zárójelpár között jelenítjük meg, amelyeket a Unicode sorszámaikkal határoztunk meg.

A teszteredmények azt mutatják, hogy az osztályunkban jól definiáltuk a szükséges metódusokat, a belőle származó példány valóban sorozat típusú konténerként viselkedik.

A következő bejegyzésben továbbfejlesztjük ez azt osztályt úgy, hogy képes legyen a szeletképzésre és egy másik sorozattal való összefűzésre.

E bejegyzéshez kapcsolódó ismeretanyag a Python tudásépítés lépésről lépésre című e-könyvben elsősorban a „Speciális metódus- és adatattribútumok”, valamint az „Osztály vigyázz! – típuslétrehozás osztályokkal” című fejezetekben található.

É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.