Hlavní navigace

Létající cirkus (7)

Jan Švec

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?