Black Friday je tu! Využij jedinečnou příležitost a získej až 80 % znalostí navíc zdarma! Více zde
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í.
BF extended 2022

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 324x (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í:
71 hlasů
(^_^)
Aktivity

 

 

Komentáře
Zobrazit starší komentáře (10)

Avatar
hanpari
Tvůrce
Avatar
hanpari:3.2.2015 20:31

Ahoj, napřed trochu organizační. Dávej prosím tlačítko odpovědět, ať vím, že mám reagovat.

Jinak se samozřejmě s tebou nepřu. Pokud už musím používat třídy (a ne že bych se tomu nevyhýbal :) ), pak bych použil dekoratér property.

Na druhou stranu je fakt, že když o tom tak přemýšlím, všechny moduly, se kterými jsem naposled dělal, buď vlastnosti neměly vůbec, nebo je mají nějak prapodivně, viz například tkinter nebo turtle.

Takže asi záleží na vkusu. Moje je doporučení ohledně OOP: když to není nutné, tak se mu vyhněte :)

 
Odpovědět
3.2.2015 20:31
Avatar
David.Landa
Člen
Avatar
Odpovídá na hanpari
David.Landa:3.2.2015 21:47

Díky za reakci, doufám, že to takhle je OK.

 
Odpovědět
3.2.2015 21:47
Avatar
Otík Rampouch:20.6.2018 14:29

Pokud byste to někdo zkoušeli jako já ve dvojkovém pythonu, tak třída, která má vevnitř @properties dekorátor, musí dědit z object. Třeba tím někomu ušetřím pár minut času stráveného hledáním.

 
Odpovědět
20.6.2018 14:29
Avatar
SVARCICEK
Člen
Avatar
SVARCICEK:7.1.2020 14:10

Jenom borci, opravte to vek=18 na vek=>18

 
Odpovědět
7.1.2020 14:10
Avatar
Filip Širůček:11.10.2020 19:38

Ahoj pro pochopení logiky určitě super ale mám věcný dotaz nebo poznámku. Co kdybych použil pro ukládání hodnot databázi a pro zachování vnitřního stavu bych použil třeba kalkulované pole nebo trigger na tabulce Studenti. Pak se o to nemusí starat python, databáze má k tomu taky hodně nástrojů (constraints atd). Ale to je obecně otázka o co se stará při ukládání aplikace a o co třeba databáze. Sám nevím kdybych co použil protože nejsem db vývojář. Asi je to teda o tom kde je aplikační logika jestli v aplikaci nebo v databázi nebo částečně tam i tam. Díky za názory.

 
Odpovědět
11.10.2020 19:38
Avatar
Antonín Martykán:14.5.2021 10:18

Nerozumím následujícím řádkům:

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

Nejdřív jsme vytvořili vlastnost, (dekorovali jsme ji @property) která umožní navrátit privátní atribut. Proč bychom teď měli používat další dekorátor @nazev_vlastnos­ti.getter, který bude dělat to samé jako už vytvořená vlastnost? (= vracet privátní atribut)

 
Odpovědět
14.5.2021 10:18
Avatar
Jakub Vokáč:14.11.2021 13:27

Ahoj.
Pokud chci k privatnim atributum objektu pristupovat pres slovnik __dict__ jak je uvedeno v clanku:

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

ocekaval bych, ze v pripade, kdy mam nejakou takovou obecnou tridu (a objekt 'x' z ni vytvoreny):

class Trida:
    def __init__(self, hodnota):
        self.__cislo = hodnota

x = Trida(666)

je klic k hodnote priv. atributu "__cislo"
Ale neni. Je to "_Trida__cislo":

print(x.__dict__['__cislo']) # vyhodi vyjimku KeyError: '__cislo'
print(x.__dict__['_Trida__cislo']) # vypise ocekavane 666

Da se na to prijit tim ,ze se necha vypsat cely slovnik:

print(x.__dict__) # vypise slovnik {'_Trida__cislo': 666}

Syntaxe nazvu klice je shodna s v clanku uvedenou technikou komoleni jmen:

x._Trida__cislo

V clanku je ovsem syntaxe, ktera nefungovala:

objekt.__NazevTridy_nazevatributu

Funkcni byla az po prohozeni podtrzitek. Prvni jednoduche, druhe dvojite:

objekt._NazevTridy__nazevatributu

Myslim, ze by se tyto informace mohly nekomu dalsimu hodit.
Je to tak v pythonu 3.8.10.

 
Odpovědět
14.11.2021 13:27
Avatar
Ctibor Hlaváč:6. června 16:13

Jestli to bylo pro to, aby student musel použít hlavu, pak je to geniální.

def vrat_plnoletos­t(self):
return self.__plnoletost
mělo by být =>
def vrat_plnoletos­t(self):
return self.__plnolety

 
Odpovědět
6. června 16:13
Avatar
Marie Formánková:24. listopadu 15:31

První kód třídy student mi nepřijde správně zapsán - háže mi to SyntaxError: invalid syntax

 
Odpovědět
24. listopadu 15:31
Avatar
Marie Formánková:24. listopadu 15:36

*chyba nalezena, takže beru zpět :)

 
Odpovědět
24. listopadu 15:36
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 20. Zobrazit vše