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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class Sorozat: def __init__(self, iterobj): self.indexek_elemek = {} for i, e in enumerate(iterobj): self.indexek_elemek.update({i: e}) def __repr__(self): return f'{type(self).__name__}(({", ".join(str(v) for v in self.indexek_elemek.values())}))' def __str__(self): return f'{chr(0x3010)}{", ".join(str(v) for v in self.indexek_elemek.values())}{chr(0x3011)}' def __contains__(self, elem): return elem in self.indexek_elemek.values() def __len__(self): return len(self.indexek_elemek) def __getitem__(self, index): if not isinstance(index, int): raise TypeError('Index csak egész szám lehet') # Nemnegatív index használata. nni = index if index >= 0 else len(self) + index # Ellenőrzés, hogy érvényes indexértékről van-e szó. if nni not in self.indexek_elemek: raise IndexError # Ha érvényes a nemnegatív index, akkor a belső szótárból e kulcsnak # megfelelő értéket kikérjük és ezt adja vissz a metódus. return self.indexek_elemek[nni] def __reversed__(self): return (self[len(self) - 1 - i] for i in range(len(self))) def forgat(sorozat, n: int): hossz = len(sorozat) return type(sorozat)((sorozat[(i + n) % hossz] for i in range(hossz))) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# TESZT # A sorozatkonténer létrehozása. sr = Sorozat((chr(i) for i in range(65, 65 + 8))) # Karaktersorozat reprezentációk. print(repr(sr), sr) # Eredmény: Sorozat(A, B, C, D, E, F, G, H) 【A, B, C, D, E, F, G, H】 # Tartalmazásvizsgálat. print('C' in sr) # Eredmény: True # A sorozat hossza. print(len(sr)) # Eredmény: 8 # Adott indexű elemek kikérése. print(sr[2], sr[-2]) # Eredmény: C G # Fordított sorrendű elemekből létrehozott sorozat. print(Sorozat(reversed(sr))) # Eredmény: 【H, G, F, E, D, C, B, A】 # Elemek balraforgatásával előálló sorozat. print(forgat(sr, 2)) # Eredmény: 【C, D, E, F, G, H, A, B】 |
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ó.