Hlavní navigace

Létající cirkus (6)

7. 3. 2002
Doba čtení: 6 minut

Sdílet

Jak jsem již slíbil na konci minulého dílu, podíváme se nyní na další oblast, ve které Python dominuje - objektově orientované programování. Dnes poprvé porušíme pravidlo a budeme se věnovat nejnovější verzi 2.2, která v oblasti OOP přináší v tomto jazyce další pokrok.

Úvod

Jak již bylo na mnoha místech napsáno, Python NENÍ plně objektový jazyk, obsahuje však ty nedůležitější vlastnosti OOP – dědičnost (samozřejmostí je i vícenásobná dědičnost), polymorfismus a zapouzdření. Objekt není v Pythonu chápán pouze jako instance třídy, ale jde o data určitého typu uložená v paměti. Pouze některé objekty jsou instancemi některé třídy, jiné mohou být uživatelské funkce, moduly nebo jednoduché typy (čísla, řetězce, tuple, seznamy…). Objektem je taktéž třída.

Každý objekt může mít množství atributů a metod. V Pythonu je každý atribut (jako atribut bereme i metodu) veřejný (public), čili libovolný objekt má přístup k atributům jiného objektu. Přeje-li si programátor třídy skutečně soukromé (private) atributy, musí třídu implementovat jako rozšiřující modul v jazyce C/C++.

Definování třídy

Třídu definujeme pomocí konstrukce class:

>>> class pes:
...     'Obecny domaci mazlicek'
...     def __init__(self, b = 'cerna'):
...         self.barva = b
...     def ukaz(self):
...         print 'Toto je pes:', self
...         print 'Barva srsti:', self.barva
...

Po klíčovém slově class následuje jméno třídy a celé tělo definice třídy je odsazeno, stejně jako při použití kteréhokoli jiné složené konstrukce (definice funkce, konstrukce if atd.). Pro tělo definice třídy se vytvoří nový lokální prostor jmen a tento prostor jmen je zachován i po opuštění těla definice.

Konstrukce class vytvoří nový objekt typu třída. Všechny objekty tohoto typu podporují dvě operace – odkazování se na atributy (pomocí tečkové notace) a vytváření instancí:

>>> pes.ukaz                            # (1)
<unbound method pes.ukaz>
>>> pes.__doc__                         # (2)
'Obecny domaci mazlicek'

Atributy objektu třída jsou vlastně jména z lokálního prostoru jmen, který se vytvořil při definici funkce. Jak je vidět z řádku (2), i třídy mohou mít dokumentační řetězce. Každá třída má ještě pár speciálních atributů, které jí přiřadil Python:

Tabulka č. 258
__name__ Jméno třídy jako řetězec
__bases__ Tuple všech předků třídy
__doc__ Dokumentační řetězec třídy
__module__ Jméno modulu, ve kterém je třída definována

Instance třídy se vytvářejí „zavoláním“ třídy (podobně jako volání funkce), přičemž toto „volání“ vrátí instanci třídy:

>>> cerny = pes()                       # (1)
>>> bily = pes('bila')                  # (2)
>>> cerny.ukaz()                        # (3)
Barva srsti naseho pejska je cerna
>>> bily.ukaz()                         # (4)
Barva srsti naseho pejska je bila
>>> pes.ukaz(cerny)                     # (5)
Barva srsti naseho pejska je cerna
>>> pes.ukaz()       # CHYBNĚ!!!        # (6)

Každá metoda v Pythonu má ještě jeden argument navíc – většinou je pojmenován self – pomocí něhož se předává instance, pro kterou byla tato metoda zavolána. Tento argument musí být použit u každé (!) metody, která se definuje v těle třídy, a musí se jednat o její první argument. Python nepoužívá žádného zkráceného zápisu pro přístup k instančním atributům, vždy se musí používat zápisu self.atribut (viz řádek (4) v definici třídy pes). Toto opatření zvyšuje čitelnost kódu a zabraňuje kolizím mezi lokálními proměnnými a proměnnými instance.

Máme-li vytvořenu instanci třídy, odkazujeme se na její atributy pomocí tečkové notace. Voláme-li metodu této instance, postupujeme stejně jako při volání obyčejné funkce, přičemž za argument self interpret automaticky dosadí tuto instanci. Každé volání metody se ve skutečnosti převede na volání funkce, která je definována v těle třídy. Takže volání cerny.ukaz() se převede na pes.ukaz(cerny). Je ale důležité si uvědomit, že cerny.ukaz a pes.ukaz není jedno a totéž, v prvním případě se jedná o metodu třídy pes, ve druhém o funkční objekt, definovaný uvnitř třídy pes. S metodami je možné pracovat úplně stejně jako s funkcemi, je možné je předávat v místech, kde se očekávají funkce apod. Není dovoleno volat metodu bez instance třídy, řádek (6) tudíž vede k výjimce. (Pozn.: Od verze 2.2 je možné již vytvářet třídní metody, které nevyžadují instanci třídy, viz dále.)

Při vytvoření nové instance třídy je zavolána speciální metoda této třídy __init__, jíž jsou předány ty argumenty, které třída dostala při žádosti o vytvoření nové instance. Jde o obdobu konstruktoru v C/C++. Python podporuje i další speciální metody (zde je uveden pouze krátký nástin problému, více viz dokumentace k jazyku). Stejný princip se používá i při přetěžování operátorů.

Tabulka č. 259
__del__ Destruktor, volán při smazání objektu garbage collectorem
__str__ Zavolána při žádosti o získání popisku objektu vestavěnou funkcí str()
__getattr__ Zavolána při čtení atributu objektu
__setattr__ Zavolána při nastavení hodnotu atributu

Veškerá jména definovaná uvnitř prostoru jmen třídy se šíří i do jednotlivých instancí, proto cerny.__doc__ je totéž jako pes.__doc__.

>>> class test:                                  # (1)
...     value = 'definovano ve tride'            # (2)
...     def __init__(self):                      # (3)
...         self.value = 'definovano v instanci' # (4)
...                                              # (5)
>>> obj = test()                                 # (6)
>>> test.value                                   # (7)
'definovano ve tride'
>>> obj.value                                    # (8)
'definovano v instanci'

Na řádku (8) je vidět, jak přiřazení hodnoty někde v instanci skryje hodnotu přiřazenou ve třídě. Přitom se původní hodnota definovaná uvnitř třídy nezmění, viz. řádek (7). Toho se využívá například při definici výchozích hodnot, které jsou společné pro všechny instance dané třídy.

Dědičnost

Nyní máme definovánu třídu pes a chceme od ní odvodit novou třídu, která bude navíc popisovat délku srsti psa.

>>> class jezevcik (pes):                     # (1)
...     'Dlouhosrsty jezevcik'                # (2)
...     def __init__(self, d = 4):            # (3)
...         pes.__init__(self, b = 'hneda')   # (4)
...         self.delka = d                    # (5)
...     def ukaz(self):                       # (6)
...         pes.ukaz(self)                    # (7)
...         print 'Delka srsti:', self.delka  # (8)
...
>>> punta = jezevcik()                        # (9)
>>> punta.ukaz()                              # (10)
Toto je pes: <__main__.jezevcik instance at 0x82a6a8c>
Barva srsti: hneda
Delka srsti: 4

V Pythonu je možné používat i vícenásobnou dědičnost, pak se v závorkách na řádku (1) objeví jednotlivé rodičovské třídy oddělené čárkou. Všechny metody v Pythonu jsou automaticky virtuální (viz. řádek (10), kde se volá metoda třídy jezevcik a ne třídy pes). Vyplývá to ze způsobu, jakým Python postupuje při hledání atributu (a tudíž i metody) objektu. Ten nejprve prohlédne třídu objektu, pak první rodičovskou třídu této třídy, totéž udělá s dalšími předky této třídy, není-li zde atribut nalezen, pokračuje u další rodičovské třídy atd. Použije se první nalezený atribut (nebo metoda, což je totéž). Nenajde-li interpret žádný odpovídající atribut, dojde k výjimce.

Chceme-li volat metodu některé z rodičovských tříd, použijeme zápis podobný tomu, jako je na řádku (7), zavoláme funkci ukaz třídy pes pro objekt self. Podobný zápis jsme mohli vidět i na řádku (4), kde inicializujeme instanci pomocí rodičovského konstruktoru.

Pár slov o instancích

Jak instance vznikne, jsme si již řekli. Její odstranění není již tak jednoduchou záležitostí, protože Python má zabudovaný garbage collector založený na počítání odkazů. Instance třídy jsou proto odstraněny, až když na ně neexistuje žádný odkaz. Proto může každá třída definovat podobně jako metodu __init__ i metodu __del__, která je volána při odstranění instance.

root_podpora

Instancím přiděluje interpret také speciální atribut __class__, který odkazuje na třídu objektu. Python také obsahuje dvě vestavěné funkce, které jsou užitečné v objektově orientovaném programování:

  • isinstance(object, classinfo) – návratová hodnota je true, platí-li, že argument object (instance třídy) je potomkem (přímým i nepřímým) argumentu classinfo (třídy), ve verzi 2.2 může classinfo být i tuple, který obsahuje třídy, pak je návratová hodnota true, je-li object potomkem alespoň jedné třídy v classinfo.
  • issubclass(class1, class2) – vrátí true, platí-li, že třída class1 je potomkem třídy class2. Je-li class1 rovno class2, funkce vrátí true.

Příště

Do dnešního dílu se již bohužel nevešla část věnovaná rozšířením objektového modelu, kterou přináší Python ve verzi 2.2. Mohu vám ale slíbit, že příští díl bude věnován jen a jen této problematice. Můžete se těšit na třídní a statické metody a na mechanismus vlastností (properties). Také se vrátíme k výkladu metod __getattr__ a __setattr__.

Byl pro vás článek přínosný?