Hlavní navigace

Létající cirkus (7)

Jan Švec 18. 3. 2002

Jak jsem již slíbil v minulé části našeho seriálu, dnes se podíváme na rozšíření, která v objektově orientovaném modelu jazyka Python přináší jeho nejnovější verze 2.2. V dnešním dílu se také vrátíme k výkladu metod __getattr__ a __setattr__, o nichž jsme se zmínili v minulém díle.

Dědění od vestavěných typů

Od verze 2.0 prodělal Python množství změn a bylo odstraněno několik „neduhů“, které mu byly vytýkány. Jedním z nich byla nemožnost dědit od vestavěných typů. V současné době je každý vestavěný typ třídou a je možné předefinovat jeho chování klasickou cestou pomocí speciálních metod.

Jako příklad si ukážeme definování vlastního typu MyInt, který bude potomkem klasického integeru, při převodu na řetězec funkcí str() však vrátí upravený text. Veškeré vestavěné typy (ty, které definuje samotný interpret a které jsou implementovány v C/C++) najdeme uvnitř modulu types. Příklad je všeříkající a nemá smysl ho vysvětlovat:

>>> import types
>>> class MyInt (types.IntType):
...     def __str__(self):
...         return 'toto je integer s hodnotou ' + `self`
...
>>> cislo = MyInt(21)
>>> str(cislo)
'toto je integer s hodnotou 21'

Třídní a statické metody

Novinkou tak horkou, že není uvedena ani v originální dokumentaci. jsou třídní a statické metody. Třídní metoda je taková metoda, která dostane jako první argument místo instance třídu samotnou, přičemž je jedno, je-li volána jako metoda instance nebo jako funkce, která je definována ve funkci. Pro vytvoření třídní metody vytvoříme uvnitř třídy klasickou funkci, jejímž prvním argumentem bude odkaz na třídu, a poté ji pomocí funkce classmethod() „zkonvertujeme“:

>>> class c:
...     value = 'class'
...     def __init__(self):
...         self.value = 'instance'
...     def print_instance(self):
...         print self.value
...     def print_class(cls):
...         print cls.value
...     print_class = classmethod(print_class)
...
>>> ins = c()                           # (1)
>>> ins.print_instance()                # (2)
instance
>>> ins.print_class()                   # (3)
class
>>> c.print_class()                     # (4)
class
>>> c.print_instance()    # CHYBNĚ!!!   # (5)

Z řádků (3) a (4) je vidět, že je úplně jedno, voláme-li třídní metodu jako metodu instance nebo jako metodu třídy. Vždy dojde k tomu, že se za argument cls funkce print_class dosadí třída c. Odvodíme-li nyní od třídy c novou třídu d a zavoláme pro ni (nebo pro její instanci) metodu print_class, dosadí se za atribut cls třída d. Na řádku (5) je vidět chybný zápis, kdy voláme instanční metodu, jako by to byla metoda třídy. Tento zápis vede k chybě, kdy si interpret stěžuje na chybějící instanci třídy.

Výše nastíněný mechanismus třídních metod doplňují ještě statické metody. To jsou přesně ty statické metody, jak je jistě znáte z C++ nebo z Javy. Ty nepřebírají žádný argument reprezentující instanci nebo třídu. Jde o jakési funkce definované uvnitř třídy. Vytvářejí se podobně jako třídní metody funkcí staticmethod(), které předáme funkční objekt definovaný někdy dříve.

Mechanismus vlastností – properties

Věcí, která se před příchodem 2.2 musela složitě programovat a kterou řešil téměř každý programátor, byly vlastnosti (properties, netřeba představovat například programátorům v Delphi), k jejichž hodnotám je přístup zprostředkován pomocí metod. Při čtení atributu je vrácena návratová hodnota přiřazené funkce, podobně je tomu i při zápisu a smazání atributu.

Jednoduchý příklad:

>>> class prop(object):
...     def __init__(self): self._value = None
...     def _get_value(self):
...         print '_get_value()'
...         return self._value
...     def _set_value(self, value):
...         print '_set_value()'
...         self._value = value
...     def _del_value(self):
...         print '_del_value()'
...         print '_value nesmazana'
...     value = property(_get_value, _set_value, \
... _del_value, 'Ukazka properties')
...
>>> obj = prop()
>>> obj.value = 'text'
_set_value()
>>> obj.value
_get_value()
'text'
>>> del obj.value
_del_value()
_value nesmazana
>>> prop.value.__doc__
'Ukazka properties'

Poměrně rozsáhlý příklad, ale veškeré jádro pudla je obsaženo v jediných dvou věcech:

  1. Třída, v níž chceme properties používat, musí být potomkem typu object. Bez splnění této podmínky nebudou properties pracovat správně.
  2. Vlastnost vytvoříme voláním funkce property, která přebírá tyto nepovinné argumenty:

    fget – metoda pro získání hodnoty vlastnosti
    fset – metoda pro nastavení hodnoty vlastnosti
    fdel – metoda, která zařídí vymazání vlastnosti z prostoru jmen
    doc – dokumentační řetězec vlastnosti

    Nebude-li některý argument nastaven, bude daná akce zakázána a při pokusu o její provedení dojde k výjimce.

Metody __getattr__ a __setattr__

Jak jsme si řekli v minulém dílu našeho povídání, je možné vytvořit vlastní metody, které zprostředkovávají přístup k atributům objektu. Na stejném principu je založen i mechanismus vlastností, o kterém jsme se již zmínili. Nejprve si ukážeme příklad:

>>> class Double:                                 # (1)
...     def __init__(self, val = 0):              # (2)
...         self.value = float(val)               # (3)
...                                               # (4)
...     def __getattr__(self, name):              # (5)
...         if name == 'double':                  # (6)
...             return self.value * 2             # (7)
...         raise AttributeError, name            # (8)
...                                               # (9)
...     def __setattr__(self, name, value):       # (10)
...         if name == 'double':                  # (11)
...             self.value = value / 2.0          # (12)
...             return                            # (13)
...         self.__dict__[name] = value           # (14)
...
>>> i = Double(5)
>>> i.value
5.0
>>> i.double
10.0
>>> i.double = 20
>>> i.value
10.0
>>> dir(i)
['__doc__', '__getattr__', '__init__', '__module__',
'__setattr__', 'value']

Definovali jsme si třídu Double, která má dva atributy, první ‚value‘ je skutečným atributem, druhý ‚double‘ je dynamickým atributem počítaným z hodnoty value pomocí metody __getattr__. Všimněte si, že dynamické atributy (to jest ty, které jsou zpřístupněny pomocí metody __getattr__) nejsou obsaženy ve výpisu atributů pomocí vestavěné funkce dir().

Metoda __getattr__ společně s metodami __setattr__ a __delattr__ jsou nástroji pro vytváření dynamických atributů. Každá třída může definovat libovolnou z těchto metod. Metoda __getattr__ přebírá kromě odkazu na instanci i jeden argument name, jenž je jménem, které chce interpret zpřístupnit. Tato metoda je spouštěna pouze tehdy, je-li požadován přístup k nějakému atributu, který není nalezen standardním postupem (čili není přímo součástí instance nebo třídy). Důvodem pro takové chování je především rychlý přístup k atributům instancí tříd, které používají jak klasické, tak dynamické atributy. Odkazujeme-li se uvnitř metody __getattr__ na další atributy téže instance, a ty nejsou nalezeny standardním postupem, je __getattr__ volána znova. Proto je třeba dávat veliký pozor na ukončení rekurze. Jako konečná hodnota atributu se použije návratová hodnota metody __getattr__. Není-li atribut nalezen, je slušné vyvolat výjimku (viz řádek (8)), jinak by se jako hodnota atributu použila hodnota None, což je implicitní návratová hodnota, nepoužila-li metoda klíčové slovo return nebo bylo-li použito bez parametrů.

Metoda __setattr__ je volána VŽDY při nastavení hodnoty určitého atributu. Zde je určitá asymetrie mezi metodami __getattr__ a __setattr__ a je dobré ji mít vždy na paměti. Metoda __setattr__ přebírá dva argumenty. Argument name je jméno atributu, který se má změnit, a value obsahuje novou hodnota atributu. Přiřadíme-li uvnitř metody __setattr__ nějakou hodnotu libovolnému atributu téže instance, je metoda __setattr__ volána znovu. Jestliže by výše uvedená metoda __setattr__ vypadala například takto:

def __setattr__(self, name, value):
    if name == 'double':
        self.value = value / 2.0
        return
    if name == 'value':
        self.value = value        # !!!
    self.__dict__[name] = value

došlo by k nekonečnému cyklu. Proto se musí používat atribut instance __dict__, což je vlastně asociativní pole, které obsahuje jména atributů (klíč) a jejich hodnoty. Klíčové slovo return je použito pro předčasné ukončení metody, aby nedošlo k zařazení atributu ‚double‘ do pole __dict__, a tím k vytvoření klasického atributu (metoda __getattr__ by pak již nikdy nebyla pro tento atribut volána).

Existuje ještě metoda __delattr__, která je volána vždy při smazání atributu konstrukcí del. Ta přebírá, podobně jako __getattr__, jeden argument name, obsahující jméno atributu, který se má smazat. Opět není možné použít přímé smazání hodnoty konstrukcí del self.attribute, ale musí se smazat odpovídající položka atributu __dict__.

Metody __*attr__ musí být známé již v době překladu kódu, toto opatření je zavedeno kvůli zvýšení rychlosti běhu při interpretaci takového kódu. Závěrem je důležité dodat, že programování dynamických atributů je poměrně obtížné, daleko výhodnější a jednodušší je používat mechanismus vlastností, které přináší verze 2.2.

O problematice objektů se lze více dozvědět z originálního tutorialu k Pythonu. Ovšem nedozvíte se z ní o nejnovějších rysech jazyka. U rysů, jako jsou třídní metody nebo vlastnosti, jsem čerpal z docstringů funkcí classmethod(), staticmethod() a property(). Verze 2.2 je stále ještě poměrně mladá, a proto prosím každého, kdo by můj výklad mohl upřesnit, případně odkázat na další zdroje, nechť dá vědět v diskusi pod článkem.

Příště

Dnešním dnem jsem zvládli nejrozsáhlejší část Pythonu – objektově orientované programování. V příštím dílu se podíváme na výjimky. Ukážeme si, jak vznikají, jak je odchytit a jak definovat svoje vlastní výjimky.

Našli jste v článku chybu?

27. 10. 2008 0:23

rw (neregistrovaný)
Pro zacinajici pythonaky (jako jsem ja) pridavam neco z poslednich novinek verze 2.6 (protoze staticmethod = stacicmethod(..) me poradne vydesilo ..skoro tak, ze jsem uvazoval zda je python to prave)
class Bla(object):

    def __init__(self): 
        self.a = 'abvc'
        self.voltage = 'ac'

#class metoda -----

    @classmethod
    def saySomethingClassic(cls):
        print('hi')
        print(cls)

#static metoda -----

    @staticmethod
    def…

19. 3. 2002 10:26

gmmns (neregistrovaný)

Generatory bych tam urcite dal, ale spis az po tech vyjimkach a syntaxi. Serial je fajn, drzim palce do dalsich dilu.

Vitalia.cz: Jak vybrat ořechy do cukroví a kde mají levné

Jak vybrat ořechy do cukroví a kde mají levné

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

120na80.cz: Rovnátka, která nejsou vidět

Rovnátka, která nejsou vidět

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Podnikatel.cz: Na poslední chvíli šokuje výjimkami v EET

Na poslední chvíli šokuje výjimkami v EET

Podnikatel.cz: Babiš: E-shopy z EET možná vyjmeme

Babiš: E-shopy z EET možná vyjmeme

DigiZone.cz: Flix TV má set-top box s HEVC

Flix TV má set-top box s HEVC

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Lupa.cz: Seznam mění vedení. Pavel Zima v čele končí

Seznam mění vedení. Pavel Zima v čele končí

DigiZone.cz: ČT má dalšího zástupce v EBU

ČT má dalšího zástupce v EBU

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu