A feladat tehát az, hogy egy osztályt úgy definiáljunk, hogy annak példányaiban bizonyos adatattribútumok írhatók és olvashatók, ugyanakkor más adatattribútumok csak olvashatók legyenek. Ez az igény olyan esetekben fordulhat elő, amikor a modellezett entitásnak vannak olyan jellemzői, adatai, amelyek a születése, létrejötte után nem változnak, és vannak olyanok, amelyek változhatnak.
Vegyünk például egy embert, egy személyt. Születése után a személy rendelkezik olyan adatokkal, amelyek élete végéig változatlanok. Ilyen mindenképpen a születési helye és ideje. De általában ilyen a neve és a nemzetisége is (most az egyszerűség kedvéért a névváltoztatás ritka esetétől tekintsünk el). Ezzel együtt van számos más jellemző, ami változik vagy változhat a személy élete során. Ilyenek lehetnek a fizikai jellemzők mint pl. a magasság, súly vagy hajszín.
Ha egy személyt akarunk modellezni egy Person nevű osztállyal, akkor érdemes a felülírhatóság szempontjából megkülönböztetni az attribútumokat, és úgy megalkotni az osztályt, hogy az élethossz során nem változó jellemzők csak olvasható attribútumokként szerepeljenek, a többihez pedig biztosítsuk a módosíthatóságot. Ez utóbbi esetén általában illik a kontrollált módosíthatóságot biztosítani, vagyis azt, hogy az értékadás tényleges megtörténtét megelőzően lehessen érték-, típus- vagy bármi más ellenőrzést végezni.
Mindezt általában tulajdonságok (property) definiálásával szokás megtenni, ahol a nem változtatható tulajdonságok csak getter metódussal, a módosíthatók pedig getter és setter metódusokkal egyaránt rendelkeznek. Ezt láthatjuk alább, ahol a Person osztályban a név, a születési hely és idő, valamint a nemzetiség csak olvasható tulajdonságként szerepel. Az életkorral változó magasság viszont írható tulajdonság, ahol az elfogadható magasságértéket ellenőrizzük.
|
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 42 43 44 |
class Person: def __init__(self, name, birthplace, birthdate, nationality, height): self._name = name self._birthplace = birthplace self._birthdate = birthdate self._nationality = nationality self.height = height @property def name(self): return self._name @property def birthplace(self): return self._birthplace @property def birthdate(self): return self._birthdate @property def nationality(self): return self._nationality @property def height(self): return self._height @height.setter def height(self, value): if 0 < value < 300: self._height = value else: raise ValueError('Nem emberi magasságérték') def introduce_yourself(self): print(f'{self.name} vagyok, {self.nationality} állampolgár. Születtem: ' f'{self.birthplace}, {self.birthdate}. ' f'Magasságom: {self.height}cm.') # TESZT person = Person('Kis Bence', 'Cegléd', '2016.07.26', 'magyar', 124) person.introduce_yourself() # Eredmény: # Kis Bence vagyok, magyar állampolgár. Születtem: Cegléd, 2016.07.26. Magasságom: 124cm. |
Ez a megoldás alapvetően megfelel a követelményeknek. Azonban van két nem előnyös vonása. Az egyik, hogy ha minél több a nem változtatható attribútumok száma, annál több getter metódusra van szükség az osztályban, ami a kód áttekinthetőségét csökkenti. A másik, hogy a getter metódusok a háttérben egy privátnak jelölt attribútum értékét adják vissza. Mint tudjuk, Pythonban az, hogy egy attribútum privát csak egy jelölési konvencióval valósul meg, valójában, ha valaki ismeri a privát attribútum nevét, akkor felül tudja írni. Márpedig egy objektum attribútumainak nevei könnyen megtudhatók.
Kérdés, hogy lehet-e olyan megoldást találni, amely e két hátrányt kiküszöböli?
Igen, mégpedig a collections modul namedtuple konténerének segítségével. Ez egy olyan speciális tuple, amelynél az elemek nem csak indexszel, hanem az értékekhez rendelt nevekkel, más szóval mezőnevekkel, a normál attribútumeléréshez használt szintaxis formájában is hozzáférhetők. Mivel tuple-ról van szó, ami változtathatatlan konténer, ezért az értékek csak kiolvashatók, de nem írhatók. E két jellemzőjét, vagyis a csak olvashatóságot, és az attribútumelérési formájában történő értékhozzáférést fogjuk kihasználni. Mégpedig úgy, hogy a Person azon attribútumaiból, amelyeket csak olvashatónak szánunk, egy mezőneves tuple típust hozunk létre, és a Person osztály ebből fog örökölni. A Person többi, módosítható attribútumait írható tulajdonságként definiáljuk. Eddig egyszerűen hangzik, de egy dologra még oda kell figyelni. Mégpedig arra, hogy mivel nem változtatható konténerből öröklünk, a példányok adatattribútumainak létrehozása és inicializálása a szokásos módon, vagyis az __init__-en keresztül, nem működik. Helyette a __new__ definiálása szükséges.
A Person osztály ezen megfontolásokkal kialakított definícióját mutatja a következő programkód.
|
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 42 43 |
from collections import namedtuple class Person(namedtuple('PersonNT','name birthplace birthdate nationality')): def __new__(cls, name, birthplace, birthdate, nationality, height): instance = super().__new__(cls, name, birthplace, birthdate, nationality) instance.height = height return instance @property def height(self): return self._height @height.setter def height(self, value): if 0 < value < 300: self._height = value else: raise ValueError('Nem emberi magasságérték') def introduce_yourself(self): print(f'{self.name} vagyok, {self.nationality} állampolgár. Születtem: ' f'{self.birthplace}, {self.birthdate}. ' f'Magasságom: {self.height}cm.') # TESZT person = Person('Kis Bence', 'Cegléd', '2016.07.26', 'magyar', 124) person.introduce_yourself() # Eredmény: # Kis Bence vagyok, magyar állampolgár. Születtem: Cegléd, 2016.07.26. Magasságom: 124cm. person.height = 170 person.introduce_yourself() # Eredmény: # Kis Bence vagyok, magyar állampolgár. Születtem: Cegléd, 2016.07.26. Magasságom: 170cm. person.birthplace = 'Pécs' # Eredmény: # Traceback (most recent call last): # ... # AttributeError: can't set attribute |
A Person példányaiban a csak olvasható attribútumok ténylegesen védettek, nem lehet őket semmilyen kerülő módon felülírni. És az is látszik, hogy az osztály definíciója sokkal áttekinthetőbb lett.
E bejegyzés témájához és megértéséhez a Python tudásépítés lépésről lépésre című e-könyv következő részei kapcsolódnak elsősorban: a „Mágikus metódusok egyedi igényre szabott osztályokban” fejezet „A példányosítási folyamat befolyásolása” alfejezete, valamint a „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezet, „Speciális konténer típusok” alfejezetén belül a „Mezőneves tuple – namedtuple” cím.