Pouze tento týden sleva až 80 % na e-learning týkající se C# .NET. Zároveň využij akce až 50 % zdarma při nákupu e-learningu. Více informací:
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 12 - Vlastnosti v Pythonu

V předešlém cvičení, Řešené úlohy k 10.-11. lekci Python, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

V dnešním Python tutoriálu se podíváme na další prvky tříd, které ještě neznáme. Začněme slíbenými vlastnostmi.

Vlastnosti

Velmi často se nám stává, že chceme mít kontrolu nad změnami nějakého atributu objektu zvenčí. Budeme chtít atribut nastavit jako read-only nebo reagovat na jeho změny. Založme si soubor (název Vlastnosti) a vytvořme následující třídu Student, která bude reprezentovat studenta v nějakém informačním systému.

class Student:
    def __init__(self, jmeno, pohlavi, vek):
        self.jmeno = jmeno
        self.muz = pohlavi
        self.vek = vek
        self.plnolety = (vek >= 18)

    def __str__(self):
        jsem_plnolety = "jsem" if self.plnolety else "nejsem"
        pohlavi = "muž" if self.muz else "žena"
        return "Jsem {0}, {1}. Je mi {2} let a {3} plnoletý.".format(
            self.jmeno, pohlavi, self.vek, jsem_plnolety)

Třída je velmi jednoduchá, student se nějak jmenuje, je nějakého pohlaví a má určitý věk. Podle tohoto věku se nastavuje atribut plnolety pro pohodlnější vyhodnocování plnoletosti na různých místech systému. K uložení pohlaví používáme booleovskou hodnotu, zda je student muž. Konstruktor dle věku určí, zda je student plnoletý. Metoda __str__() je navržena pro potřeby tutoriálu tak, aby nám vypsala všechny informace. V reálu by tam bylo pravděpodobně jen jméno studenta. Pomocí konstruktoru si nějakého studenta vytvořme:

s = Student("Pavel Hora", True, 20)
print(s)
input()

Výstup:

Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.

Vše vypadá hezky, ale atributy jsou přístupné jak ke čtení, tak k zápisu. Objekt tedy můžeme rozbít například takto (hovoříme o nekonzistentním vnitřním stavu):

s = Student("Pavel Hora", True, 20)
s.vek = 15
s.muz = False
print(s)
input()

Výstup:

Vlastnosti – tedy gettery a settery Pythonu

Určitě musíme ošetřit, aby se plnoletost obnovila při změně věku. Když se zamyslíme nad ostatními atributy, není nejmenší důvod, abychom je taktéž umožňovali modifikovat. Student si za normálních okolností asi jen stěží změní pohlaví nebo jméno. Bylo by však zároveň vhodné je vystavit ke čtení, nemůžeme je tedy pouze pouze nastavit jako private. V dřívějších dílech seriálu jsme k tomuto účelu používali metody, které sloužily ke čtení privátních atributů. Jejich název jsme volili jako vrat_vek() a podobně. Ke čtení vybraných atributů vytvoříme také metody a atributy označíme jako privátní. Třída by nově vypadala např. takto:

class Student:

    def __init__(self, jmeno, pohlavi, vek):
        self.__jmeno = jmeno
        self.__muz = pohlavi
        self.__vek = vek
        self.__plnolety = (vek >= 18)

    def __str__(self):
        jsem_plnolety = "jsem" if self.__plnolety else "nejsem"
        pohlavi = "muž" if self.__muz else "žena"
        return "Jsem {0}, {1}. Je mi {2} let a {3} plnoletý.".format(
            self.__jmeno, pohlavi, self.__vek, jsem_plnolety)

    def vrat_jmeno(self):
        return self.__jmeno

    def vrat_plnoletost(self):
        return self.__plnoletost

    def vrat_vek(self):
        return self.__vek

    def muz(self):
        return self.__muz

    def nastav_vek(self, hodnota):
        self.__vek = hodnota
        self.__plnolety = True
        if vek < 18:
            self.__plnolety = False

Metody, co hodnoty jen vracejí, jsou velmi jednoduché. Nastavení věku má již nějakou vnitřní logiku, při jeho změně musíme totiž přehodnotit atribut plnolety. Zajistili jsme, že se do proměnných nedá zapisovat jinak, než my chceme. Máme tedy pod kontrolou všechny změny atributů a dokážeme na ně reagovat. Nemůže se stát, že by nám někdo vnitřní stav nekontrolovaně měnil a rozbil.

Metodám k navrácení hodnoty se říká gettery a metodám pro zápis settery. Ruční psaní getterů a setterů je jistě velmi zdlouhavé. Nemohl by jsme si ušetřit práci? Ano, v Pythonu lze použít kratší a lepší zápis. Poté již nehovoříme o atributech, ale o vlastnostech.

Syntaxe vlastnosti je velmi podobná metodě:

@property
def jmeno(self):
    return self.__jmeno # atribut svázaný s metodou

Vytvoříme metodu, která má stejný název jako požadovaný název vlastnosti. V těle metody vrátíme atribut spojený s vlastností. Metodu dekorujeme dekorátorem property. Tím se z metody stane vlastnost. Dekorátory mají následující syntaxi:

@jmeno_dekoratoru
def nejaka_metoda(self):
    ...

Ve skutečnosti jsou dekorátory objekty podporující volání (např. funkce, metody nebo objekty s metodou __call__()), které vrací upravenou (dekorovanou) verzi metody nebo funkce. Proto lze použít i následující syntaxi:

puvodni_nazev_metody = jmeno_dekoratoru()

V Pythonu máme privátní atribut a k němu máme dvě metody, které podle kontextu Python volá (pozná dle situace, zda čteme nebo zapisujeme). Když do vlastnosti nepřidáme metodu pro setter, nepůjde měnit ani zevnitř, ani zvenčí. Vlastnost se poté používá podobně jako normální atribut:

print(objekt.nazev_vlastnosti) # čtení
objekt.nazev_vlastnosti = hodnota # zápis

Pokud si přejeme, aby se vlastnost uměla i zapisovat, než jen číst, musíme si dodefinovat i setter. Ukažme si to na našem příkladu s plnoletostí, která se musí po změně věku přehodnotit:

...
@property
def vek(self):
    return self.__vek

@vek.setter
def vek(self, hodnota):
    self.__vek = hodnota
    self.__plnolety = (hodnota > 18)

Zprvu je nutné si vytvořit privátní proměnnou __vek, ve které bude hodnota ve skutečnosti uložena. V setteru použijeme další parametr, do kterého se uloží hodnota pro přiřazení. S vek nyní pracujeme opět stejně, jako s atributem. Nenápadné přiřazení do věku vnitřně spustí další logiku k přehodnocení atributu plnolety:

s.vek = 15 # nyní se změní i plnoletost

Stejně můžeme pochopitelně implementovat i vlastní getter a například něco někam logovat:

@nazev_vlastnosti.getter
def nazev_vlastnosti(self):
    return soukromy_atribut_vlastnosti

Pokud chceme, aby se getter choval jinak, tak si tělo metody upravíme. Ovšem getter musí stále něco vracet, jinak by to nebyl getter. :)

V Pythonu se vlastnosti používají, pouze jen pokud jsou nutné. Celý program si samozřejmě můžete stáhnout pod článkem.

__dict__ a __slots__

Pokud děláme vlastnosti, tak můžeme použít jako "úložistě" atributu buď privátní atribut (viz výše), nebo veřejný atribut. Pokud ale použijeme veřejný atribut, tak se nám překryjí názvy atributu a metody vlastnosti a program upadne do rekurze. Jde to ale obejít. Veškeré atributy objektů se uchovávají ve slovníku spojeném s objektem. Dostaneme se k němu takto:

nazev_objektu.__dict__

Poté můžeme číst/přiřadit hodnotu bez rekurze:

nazev_objektu.__dict__["nazev_promenne"] # čtení
nazev_objektu.__dict__["nazev_promenne"] = hodnota # zápis

Pokud ovšem nemáme pádný důvod, aby byl atribut vlastnosti veřejný, tak je to zbytečné. Navíc je zde jedno úskalí. Takto lze měnit hodnoty i z vnějšku, aniž by nám prošly kontrolou. Ošetřit to můžeme pomocí __slots__, což by se ale nemělo vůbec použít. Podle dokumentace Pythonu bychom měli použít __slots__ maximálně pokud vytváříme velké množství instancí nějaké třídy a chceme za každou cenu ušetřit paměť.

Přesto se dá k soukromým atributům proniknout za pomoci komolení jmen - k atributu se dostaneme za pomoci následující syntaxe:

objekt.__NazevTridy_nazevatributu

Ale tohle už určitě není nevědomé získání/změnění soukromého atributu :)

V příští lekci, Magické metody v Pythonu, se podíváme na magické metody objektů.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 346x (754 B)
Aplikace je včetně zdrojových kódů v jazyce Python

 

Předchozí článek
Řešené úlohy k 10.-11. lekci Python
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Magické metody v Pythonu
Článek pro vás napsal gcx11
Avatar
Uživatelské hodnocení:
101 hlasů
(^_^)
Aktivity