10. díl - Vlastnosti v Pythonu

Python Objektově orientované programování Vlastnosti v Pythonu

V minulém dílu seriálu o Pythonu jsme si vysvětlili statiku. Dnes 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žívaly 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
        plnolety = True
        if vek < 18:
            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 toho vlastnost uměla více, než je jen načtení hodnoty, musíme si dodefinovat ony dvě metody ručně. 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 getter a například něco někam logovat.

Syntaxe getteru je:

@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. :)


 

Stáhnout

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

 

  Aktivity (1)

Článek pro vás napsal gcx11
Avatar
(^_^)

Jak se ti líbí článek?
Celkem (2 hlasů) :
4.54.54.54.54.5


 


Miniatura
Předchozí článek
Statika v Pythonu
Miniatura
Následující článek
Magické metody v Pythonu

 

 

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

Avatar
gcx11
Redaktor
Avatar
gcx11:

Předpokládám, že jako v dalším díle začnu psát o tkinteru (což je standartní knihovna většinou přibalovaná k Pythonu). Potom možná o wxPythonu nebo PyQt, které jsou pokročilejší a složitější, ale umí toho víc.

 
Odpovědět 20.10.2014 14:26
Avatar
Člen
Člen
Avatar
Člen:

Na vytvorenie aplikácie s GUI je v Pythone knižnica tkinter ;)

Odpovědět 21.10.2014 13:05
...
Avatar
David.Landa
Člen
Avatar
David.Landa:

Už jsem to psal dnes k jinému dílu (myslím 3.), ale měl sem to napsat sem: Python nemá privátní metody ani atributy. Žádný počet podtržítek nezabrání přístupu k atributu -- dvojité podtržítko přístup jen ztíží, ale obecně je to jen konvence jak označit, že není radno tento atribut používat jen tak.

Editováno 3.2.2015 14:13
 
Odpovědět 3.2.2015 14:10
Avatar
David.Landa
Člen
Avatar
David.Landa:

Je jistě lepší používat getter/setter před přímým přístupem k atributům, ale použití properties je ještě lepší! Není to jen kratší zápis get/set, vlastně zde je to dokonce více práce (musím psát @property atd.), ale je to více Pythonic, protože přistupuješ zdánlivě jakoby k atributu + pokud se změní třeba způsob validace, pak klient nic nepozná (ale to platí i pro normální set metodu).

viz "V Pythonu se vlastnosti používají, pouze jen pokud jsou nutné."

Editováno 3.2.2015 14:30
 
Odpovědět 3.2.2015 14:27
Avatar
hanpari
Redaktor
Avatar
Odpovídá na David.Landa
hanpari:

Ve skutečnosti to není tak "pythonic", jak si myslíš. Ve skutečnosti je to spíš javové :)

Pythonýrské jsou descriptory :)

http://nbviewer.ipython.org/…riteup.ipynb

Múj oblíbený kousek kódu. Všimněte si komentářů nad každou další vlastností :)

class Movie(object):
    def __init__(self, title, rating, runtime, budget, gross):
        self._rating = None
        self._runtime = None
        self._budget = None
        self._gross = None

        self.title = title
        self.rating = rating
        self.runtime = runtime
        self.gross = gross
        self.budget = budget

    #nice
    @property
    def budget(self):
        return self._budget

    @budget.setter
    def budget(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._budget = value

    #ok
    @property
    def rating(self):
        return self._rating

    @rating.setter
    def rating(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._rating = value

    #uhh...
    @property
    def runtime(self):
        return self._runtime

    @runtime.setter
    def runtime(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._runtime = value

    #is this forever?
    @property
    def gross(self):
        return self._gross

    @gross.setter
    def gross(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._gross = value

    def profit(self):
        return self.gross - self.budget
 
Odpovědět 3.2.2015 17:20
Avatar
David.Landa
Člen
Avatar
David.Landa:

Porovnávam get/set vzhledem k properties. Preferuji properties, před get/set, protože v základu dělají to samé, ale vypadají lépe, když je klient třídy používá. Get/Set je možná relikt Javy a properties C#, ale i v pythonu jsou lepší. Deskriptor je možná ještě lepší, nevím -- nepoužívám. Reagoval jsem na to, že properties jsou více Pythonic než get/set. V článku to vyznívá, že se moc nepoužívají.

Editováno 3.2.2015 18:09
 
Odpovědět 3.2.2015 18:04
Avatar
hanpari
Redaktor
Avatar
Odpovídá na David.Landa
hanpari:

V pohode ten odkaz je tutorial muzes na nej podivat

 
Odpovědět 3.2.2015 18:11
Avatar
David.Landa
Člen
Avatar
David.Landa:

Properties jsou implementované pomocí desktriptorů. Nic proti nim, já je prostě nepoužívám. Ale @property je standardní a preferované oproti get/set na tom trvám .)

 
Odpovědět 3.2.2015 18:21
Avatar
hanpari
Redaktor
Avatar
Odpovídá na David.Landa
hanpari:

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:

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

 
Odpovědět  +1 3.2.2015 21:47
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 12. Zobrazit vše