Az előző két bejegyzésben (itt és itt) sorozat típusú konténer egyéni osztályát definiáltuk, amely az alapvető kritériumok teljesítése mellett képes a szeletképzésre, valamint egy másik, ugyanilyen típusú sorozatkonténer elemeivel egy új, összefűzött sorozat előállítására. Az osztályunk így is használható, de maradt még egy műveletcsoport, amit érdemes lehet implementálni. Ez pedig nem más, mint az összehasonlító műveletek (egyenlőségvizsgálat, kisebb-nagyobb relációk).
Az, hogy ezeket meg kell-e valósítani vagy sem, természetesen a várható használati esetektől függ. Ha nem lesz soha szükség Sorozat példányok valamilyen szempontból történő egyenlőségvizsgálatára vagy rendezésére, akkor persze felesleges ezzel foglalkozni.
Ha viszont szükség van e képességekre, akkor az első lépés annak átgondolása, hogy elvben hogyan akarjuk definiálni két Sorozat egyenlőségét, illetve azt, hogy az egyik kisebb vagy nagyobb a másiknál. Ezt az elvi megfontolást nem csak a jelen feladatunkban, hanem minden saját készítésű típusnál meg kell tenni.
Az objektumok ugyanis számos attribútummal rendelkezhetnek, és ezek ismeretében meg kell határozni, hogy mely attribútum vagy attribútumok értékegyenlőségét kell vizsgálni ahhoz, hogy az adott típusú két objektumot egyenlőnek tekintsünk. És hasonlóan, ki kell jelölni mely attribútum vagy attribútumok értékeit hasonlítjuk össze a kisebb-nagyobb relációban.
Egyénileg definiált sorozatok esetén is számos módon határozhatjuk meg az egyenlőséget, attól függően, hogy a használati esetekben mi a kívánatos. Például mondhatnánk, hogy akkor tekintünk egyenlőnek két sorozatot, ha a hosszuk egyenlő. De, akár azt is mondhatnánk, hogy akkor, ha a két sorozat első és az utolsó eleme rendre egyenlő. A kisebb-nagyobb relációt pedig akár definiálhatnánk például úgy, hogy két, számelemeket tartalmazó sorozatból azt tekintjük nagyobbnak, amelyben a legnagyobb elem nagyobb, mint a másik sorozat legnagyobb eleme.
A jelen feladatunkban nem fogunk ilyen „egzotikus” összehasonlításokat meghatározni, hanem követjük a beépített sorozat típusú konténerekre (list, tuple, str, bytes, bytearray) vonatkozó szabályokat. Ezek pedig a következők:
Két sorozatot akkor tekintünk egyenlőnek, ha a típusuk megegyezik, azonos hosszúak és az azonos indexhez tartozó elemek egyenlőek. Tovább egyik sorozat akkor kisebb a másiknál, ha az elemszáma kisebb, vagy egyenlő elemszám esetén amelyiknél a növekvő indexek szerint haladva először lesz kisebb az indexhez tartozó elem értéke, mint a másiké. Például
b’1234′ == b’1234′ igaz, de b’1234′ == b’123′ és b’1234′ == b’1244′ hamis. Illetve b’123′ < b’1234′ és b’1234′ < b’3234′ igaz, de b’1234′ < b’0234′ hamis.
A Sorozat osztályunkban az egyenlőségvizsgálati képességhez az __eq__ metódust kell definiálni. A fenti definíciót követve először megnézzük, hogy az argumentumként kapott objektum Sorozat típusú-e. Ha nem, akkor azonnal False értékkel térünk vissza. Ha a típusazonosság fennál, akkor kell az elemegyenlőségeket vizsgálni. Ezt jelen esetben egyszerűen elintézhetjük, ha a belső szótárak egyenlőségvizsgálatának eredményével térünk vissza.
Alább az __eq__ metódus e tervezési megfontolásokat tükröző teljes definícióját láthatjuk. /Az előző bejegyzésekben definiált metódusoknak csak a fejlécét tüntettük fel az áttekinthetőség kedvéért. /
|
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 39 40 41 |
class Sorozat: def __init__(self, iterobj):... def __repr__(self):... def __str__(self):... def __contains__(self, elem):... def __len__(self):... def __reversed__(self):... def __getitem__(self, index):... def __add__(self, másik_sorozat):... def __eq__(self, másik_sorozat): if not isinstance(másik_sorozat, type(self)): return False return self.indexek_elemek == másik_sorozat.indexek_elemek def __lt__(self, másik_sorozat): if not isinstance(másik_sorozat, type(self)): return NotImplemented # Ha a self rövidebb sorozat, mint a másik, akkor kisebbnek tekintjük. if len(self) < len(másik_sorozat): return True # Ha a self hosszabb sorozat, mint a másik, akkor nem lehet kisebb. if len(self) > len(másik_sorozat): return False # Egyenlő hossz esetén akkor kisebb az egyik sorozat a másiknál, ha az azonos # indexhez tartozó első, nem egyenlő elemek közül az egyiké kisebb, mint a másiké. for i in range(len(self)): elem1, elem2 = self.indexek_elemek.get(i), másik_sorozat.indexek_elemek.get(i) if elem1 < elem2: return True if elem1 > elem2: return False return False def __le__(self, másik_sorozat): if not isinstance(másik_sorozat, type(self)): return NotImplemented return self == másik_sorozat or self < másik_sorozat |
A kisebb-nagyobb relációk megvalósításához némi előzetes nyelvi működési ismeret is szükséges. Egyrészt az, hogy nem szükséges minden relációhoz (kisebb, kisebb-egyenlő, nagyobb, nagyobb-egyenlő) metódus rendelni. Ugyanis, ha már definiált az egyenlőség, vagyis az __eq__ metódus, akkor elegendő csak például a kisebb és kisebb-egyenlő operátorokhoz tartozó __lt__ és __le__ metódusokat megalkotni, mert ha a nagyobb, vagy a nagyobb-egyenlő operátorral találkozik az interpreter, és nem találja az ezekhez tartozó __gt__, illetve __ge__ metódusokat, akkor azok logikai ellentétéhez tartozókat, vagyis a __le__, illetve __lt__ metódusokat kísérli meg meghívni.
A másik tudnivaló, hogy ha egy összehasonlító relációt megvalósító metódus a NotImplemented objektummal tér vissza, akkor az interpreter mielőtt véglegesen visszatérne, megkísérli a művelet logikai ellentétét megvalósító metódust meghívni megcserélt sorrendű argumentumokkal. Ha így sikerül a művelet, akkor annak eredményével tér vissza, de ha nem, akkor egy kivételt fog dobni azzal, hogy a két objektum között a kért művelet nem értelmezett.
Az előzőeket figyelembe véve, úgy döntöttünk, hogy a Sorozat osztályunkban a kisebb relációt megvalósító __lt__, valamint a kisebb-egyenlő relációt megvalósító __le__ metódust implementáljuk, amit a fenti kódban láthatunk is.
Mindkét metódusban elsőként egy típusvizsgálatot végzünk, és ha az összehasonlítandó objektum nem Sorozat típusú, akkor a NotImplemented lesz a visszatérési érték. Ezt nem azért tesszük mintha arra számítanánk, hogy a másik típusban fordított argumentumsorrenddel és ellentétes logikához tartozó metódussal hátha siker lesz. Nem. Hanem azért, hogy a hibaüzenetet az interpreterre bízzuk.
Az __lt__ metódus további kódjaival lényegében a kisebb relációra adott fentebbi meghatározást követjük le. Ezért ezt nem szükséges tovább magyarázni, és a kommentek is segítik a megértést.
Mivel az osztály már rendelkezik __eq__ és __lt__ metódusokkal, a kisebb-egyenlőt megvalósító __le__ metódus visszatérési értéke nagyon egyszerű: az egyenlőségvizsgálat, valamint a kisebb reláció logikai VAGY kapcsolatának eredményét kell visszaadnia.
Az összehasonlító operátorokkal való működés helyességét a következő tesztesetek mutatják.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# TESZT összevet = '{} {} {} : {}'.format print(összevet(s1:=Sorozat([1,2,3]), '<', s2:=Sorozat([2,2,2]), s1 < s2)) print(összevet(s1:=Sorozat([2,2,1]), '<', s2:=Sorozat([2,2,2]), s1 < s2)) print(összevet(s1:=Sorozat([2,3,1]), '<', s2:=Sorozat([2,2,2]), s1 < s2)) print(összevet(s1:=Sorozat('ABC'), '<', s2:=Sorozat('ABD'), s1 < s2)) print(összevet(s1:=Sorozat('ABD'), '<=', s2:=Sorozat('ABD'), s1 <= s2)) print(összevet(s1:=Sorozat('ABD'), '>=', s2:=Sorozat('ABD'), s1 >= s2)) print(összevet(s1:=Sorozat('ABD'), '>', s2:=Sorozat('ABC'), s1 > s2)) print(Sorozat([1,2,3]) < (1,2,4)) # Eredmények: # 【1, 2, 3】 < 【2, 2, 2】 : True # 【2, 2, 1】 < 【2, 2, 2】 : True # 【2, 3, 1】 < 【2, 2, 2】 : False # 【A, B, C】 < 【A, B, D】 : True # 【A, B, D】 <= 【A, B, D】 : True # 【A, B, D】 >= 【A, B, D】 : True # 【A, B, D】 > 【A, B, C】 : True # TypeError: '<' not supported between instances of 'Sorozat' and 'tuple' |
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ődlegesen a „Mágikus metódusok egyedi igényre szabott osztályokban” fejezet „Összehasonlító operátorok” című alfejezetében található, ahol a fentieknél több tudnivaló olvasható, és egyéb részletkérdések is tisztázódnak.