Poměrně často můžeme po datových atributech chtít, aby se chovaly trochu také jako metody, které něco dělají, a ne jen udržovaly nějakou hodnotu. Typicky:
Toto chování v mnoha jazycích zajišťují tzv. getters & setters, v Python'u je však známější pod pojmenováním properties a nebo ještě spíše pod jménem protokolu, který slouží (nejen) k jejich implementaci – deskriptory (descriptors).
color
) udržuje povědomí o barvě objektu grafického. Uvedený datový atribut je možno číst – dostanete aktuální barvu objektu. Ale též je do něj možné zapisovat – pak by se zřejmě měla zároveň změnit i samotná barva grafického objektu.
Ukažme si nejdříve poněkud „ukecaný“, leč poměrně přímočarý způsob, jak zavést na objektu atributy, které se budou chovat požadovaným způsobem:
Spustíme-li uvedený kód, obdržíme následující výstup (vysvětlující komentáře přidány):
Vestavěná funkce property()
, kterou jsme na předchozím slajdu použili pro konstrukci property-atributu barva, má v úplnosti následující záběr:
property(fget=None, fset=None, fdel=None, doc=None)
V plné šíři tedy může vygenerovat atribut, který nabídne obslužnou funkci při čtení (fget), zápisu (fset) a odstranění atributu (fdel; nepříliš častá operace) plus zavedení dokumentačního řetězce (doc).
Výše uvedené použití však není zrovna ideální, pokud obslužné funkce nepotřebujeme na nic jiného (což nejspíše bude nejčastější případ) – pokud je totiž nezavedeme jako dvojpodtržítkové (na začátku jména), tak nám budou zcela zbytečně „zaneřáďovat“ jmenný prostor objektu:
Od Python'u 2.6 se (nejen) uvedený problém zaneřáďování jmenného prostoru řeší pomocí dekorátorů. Bohužel vybraný způsob zápisu je (nejen podle mě) velmi matoucí a nepřehledný:
property
tedy slouží jako dekorátor pro getter. Zde nepoužitý dekorátor pro odstranění atributu by byl @barva.deleter
.
barva
. Všude jinde uvidíte ovšem i atribut _barva
. Chcete-li ho trochu více schovat, musíte použít podtržítka dvě:
Dosti nešťastná volba zápisu zavedení těchto „obojetných“ atributů pomocí dekorátorů způsobila zřejmě její poměrnou neoblibu mezi pythoními programátory.
Vzhledem k naprosté asymetričnosti použití dekorátorů se osobně také kloním spíše k tradičnímu používání funkce property() a důsledného dvojpodtržítkování všech pomocných funkcí a proměnných.
V kapitole o introspekci jsme viděli, že atributy objektu „žijí“ v jeho jmenném prostoru díky mapování v atributu __dict__
. Dotaz na „běžný“ (ne-property) atribut se tak přeloží pomocí metod daného objektu..
__getattribute__()
pro čtení hodnoty z atributu;
__setattr__()
pro zápis hodnoty do atributu;
__delattr__()
pro odstranění atributu;
..na odpovídající dotaz do slovníku příslušné instance nebo některé její rodičovské třídy:
Pokud se však jedná o property-atribut (tj. v odpovídající třídě je atribut definován jako property/deskriptor), všechno je jinak:
Property-atribut je ve slovníku __dict__ představován celou speciální třídou s vlastními metodami, tzv. deskriptorem.
Deskriptorové třídy definují následující metody (ne nutně všechny najednou):
__get__(self, INSTANCE, VLASTNÍK)
– volána při získávání hodnoty atributu;
__set__(self, INSTANCE, HODNOTA)
– volána při ukládání hodnoty do atributu;
__delete__(self, INSTANCE)
– volána při odstraňování atributu.
Pokus o provedení příslušné operace na property-atributu (opět samozřejmě zprostředkovaný voláním metod objektu __getattribute__()
, resp. __setattr__()
, resp. __delattr__()
) pak způsobí zavolání odpovídající „magické“ metody deskriptoru:
TŘÍDA.ATRIBUT <=> TŘÍDA.__dict__['ATRIBUT'].__get__(None, TŘÍDA)
apod.
PS: Existuje i magická metoda __getattr__()
. Narozdíl od __getattribute__(), která se volá vždy, je-li k dispozici, dochází k volání __getattr__() pouze tehdy, nepodaří-li se daný atribut nalézt jiným způsobem (a je-li definována).
Tak zvané datové deskriptory typicky definují metody __get__()
i __set__()
, nedatové deskriptory (tedy především funkce aneb metody) na druhou stranu pouze metodu __get__()
.
V praxi největší rozdíl mezi nimi je pak především ten, že metody (jakožto nedatové deskriptory) můžete na instancích „přepsat“ (jednoduše pod daným jménem uložíte něco nového), zatímco datové deskriptory nikoli (protože pokus o přiřazení vyvolá specifické chování deskriptoru). Čili především:
Deskriptory v Pyhton'u slouží k implementaci celé řady základních věcí: