Jeden z hlavních rysů jazyka Python jsme dosud nezmínili. Jedná se o objektově orientované programování (OOP). Tento "moderní" trend9.1 se v poslední době čím dál tím více rozmáhá. Některé úlohy si bez něho dokážeme pouze těžko představit (např. komplikované uživatelské rozhraní implementované pomocí strukturovaného programování by byla profesionální sebevražda). Předešleme, že Python je v otázce objektově orientovaného přístupu k návrhu programu velice pokročilý a obzvláště ve verzi 2.2 najdeme mnoho nových vlastností.
Obecně lze říci, že Python díky svému akademickému návrhu (vznikl na univerzitě v Amsterdamu) používá to nejlepší z jiných objektově orientovaných jazyků. Hlavní mechanismy byly inspirování jazykem C++ a Modula-3. Toho všeho bylo dosaženo za použití minima nové syntaxe, což umožňuje snadné pochopení objektového návrhu pomocí jazyka Python.
Python tedy jmenovitě podporuje tyto vlastnosti: dědičnost (samozřejmostí je vícenásobná dědičnost), polymorfismus (libovolnou metodu může potomek předefinovat, slovy jazyka C++ jsou všechny metody virtuální), zapouzdření - všechny předchozí vlastnosti musí podporovat každý jazyk, který si chce říkat objektově orientovaný. Další vlastnosti, které buď přímo nebo nepřímo souvisí s objektově orientovaným programováním jsou: přetěžování operátorů, mechanismus metatříd, dynamické atributy a mechanismus vlastností (properties), kooperativní volání metod, možnost odvodit třídu od interního typu atd. V dalších verzích jazyka lze počítat s dalším vývojem objektového modelu jazyka, lze předeslat, že jazyk bude podporovat rozhraní (interfaces) a ještě více se v těchto verzích setře rozdíl mezi interními typy a uživatelskými objekty.
Za objekt je v jazyce Python libovolná entita, např. řetězec, číslo,
slovník, seznam, modul ... to všechno jsou objekty. Každý objekt může mít
svoje atributy. Jak jsme již poznali, k přístupu k atributům se používá
tečková notace, čili zápis ve stylu objekt.jméno_atributu
. A
poněvadž jsme před chvílí řekli, že objekt je každá entita, i atribut je opět
objekt. Proto můžeme psát i daleko rozsáhlejší konstrukce, např.
window.button1.OnClick.handlers
apod.
Mezi atributy má zvláštní postavení metoda. Jde o funkci, která určitým způsobem patří pouze k tomuto objektu. Metody lze používat obvyklým způsobem jako klasické funkce. Jsou však většinou určeny pro modifikování vnitřního stavu objektu. Pro pochopení metod je důležité si uvědomit, že každá metoda modifikuje pouze "svůj" objekt. Podívejme se na následující kód:
>>> list1 = [1, 3, 2, 0] >>> list2 = ['ahoj', 'bbbbbbb', 'AAA', 'root'] >>> list1.sort() >>> list2.sort()
Nadefinovali jsme si dva objekty - seznamy - a následně jsme pro ně zavolali jejich metody sort(). Všimnětě si, že obě metody, i když se jmenují stejně, jsou různé, protože se každá váže k jinému objektu. Metoda tedy modifikuje pouze ten objekt, k němuž se váže, případně volá jiné metody jiných objektů. Může samozřejmě volat i jiné funkce, fantazii se meze nekladou. Pro důkladný výklad objektově orientovaného programování bych vám doporučil nějakou z odborných knich, které na toto téma vycházejí.
Pokud použijeme terminologii jazyka C++ jako základ terminologie používané v jazyce Python, můžeme říci, že všechny metody objektů jsou virtuální (čili libovolná odvozená třída může tuto metodu předefinovat, přičemž tuto novou verzi budou používat všechny ostatní metody tohoto objektu). Zároveň lze říci, že všechny atributy (tj. metody a data třídy) jsou veřejné, jinými slovy - má k nim přístup jakýkoli jiný objekt. Tato vlastnost je pro Python klíčová, nic před uživatelem neskrývá a uživatel může, pokud ví co děla, modifikovat interní data a tím docílit požadovaného (např. nestandardního) chování objektu. Veřejné jsou i pseudo-privátní atributy, které využívají mechanismu náhrady soukromých jmen (více o této problematice si povíme dále).
V Pythonu neexistují žádné konstruktory a destruktory - namísto nich zde
existuje dvojice metod __init__
a __del__
, které mají obdobnou,
ne však stejnou funkci jako konstruktory a destruktory. Jazyk Python je
zcela exaktní jazyk, čili máte pouze to, o co jste si řekli, prostředí
téměř nikdy nemyslí za vás. Proto není možné z jedné metody volat přímo
jinou metodu téhož objektu! Každá metoda proto přejímá jeden speciální argument
(většinou pojmenovaný self), který reprezentuje objekt, jehož metoda je
prováděna. Chceme-li tedy z nějaké metody vyvolat metodu téhož objektu se
jménem řekněme foo, použijeme zápisu: "self.foo()". Takto můžeme
přesně specifikovat objekt, jehož metodu chceme volat a nemůže dojít k žádným
nejednoznačnostem.
Obdobně jako v jazyce Smalltalk, i v Pythonu je třída také objekt. To ale platí v Pythonu obecně - libovolná datová struktura je objekt, objektem jsou i interní datové typy, moduly, bloky kódu, instance tříd i třídy samotné. Jako v již zmíněném Smalltalku, i Python umožňuje od verze 2.2 dědit třídy od interních datových typů, čímž lze implementovat specifické změny v chování tohoto typu (např. vytvořit datový typ odvození od seznamu, který bude sloužit pouze k uložení řetězců).
Z některých jazyků (např. C++) je známa možnost přetěžování operátorů. Python zavádí řadu "speciálních" metod, které slouží pro implementaci takto přetížených operátorů. Proto není problém nadefinovat třídu Matice a implementovat metodu __add__, která je volána při použití operátoru "+" a která tyto dvě matice sečte. Přetěžování operátorů tedy velice zprůhledňuje výsledný kód programu.
Ještě před samotným výkladem tříd si něco povíme o prostorech a oborech jmen v jazyce Python. Pochopení těchto mechanismů je důležité pro následné správné porozumění problematice tříd a objektů. Opět začneme definováním několika pojmů.
Jako prostor jmen se v Pythonu (a jiných jazycích) označuje zobrazení mezi jmény proměnných a objekty které reprezentují. Lze to chápat následujícím způsobem: objekty existují nezávisle na jejich jménech. Pokud vytvoříme nějakou proměnnou pomocí přiřazení, nevytvoříme tím nový objekt, pouze odkaz na objekt, který stojí na pravé straně přiřazení. Pokud si uvedeme následující příklad:
>>> d1 = {1: 'ahoj', 2: 'bla', 3: 'cvok'} >>> d2 = d1
Pak skutečně "d1" a "d2" odkazují na tentýž objekt. Pokud nyní modifikujeme jeden z těchto objektů, změna se projeví i ve "druhém":
>>> d2[3] = 'cyril' >>> print d1 {3: 'cyril', 2: 'bla', 1: 'ahoj'}
Jména lze tedy chápat i jako pouhé odkazy na objekty. Proto pokud předáváme nějaký objekt libovolné funkci, vždy se předává odkazem. Pokud tento objekt modifikujeme, změna se projeví i ve volající funkci.
Prostorů jmen existuje v prostředí Pythonu mnoho. Např. každý modul má svůj prostor jmen, každá definice třídy zavádí také nový prostor jmen, při každém volání funkce dojde k vytvoření nového (dočasného) prostoru jmen, dokonce každý objekt má svůj prostor jmen určený pro uchování jeho atributů atd. Jména uvnitř jednoho prostoru jmen musí být jedinečná.9.2 V různých prostorech jmen se ovšem mohou nacházet stejná jména odkazující na různé objekty.
Při každém vyvolání funkce je vytvořen nový prostor jmen, do něhož se ukládají lokální proměnné a formální parametry. Tento prostor jmen zanikne po ukončení funkce. Při rekurzivním vyvolání funkce vznikne nový prostor jmen, tudíž jednotlivá volání nesdílí společné objekty.
Jak jsme si již řekli, objekty mohou mít mnoho atributů, jejichž jména má objekt uložen ve svém prostoru jmen. Z hlediska přístupu k atributům jsou všechny atributy veřejné a dále se člení na atributy pouze pro čtení a atributy zapisovatelné. To, o jaký typ atributu se jedná se dozvíte většinou z dokumentace tohoto objektu. Například všechny atributy modulů (tj. obsah modulu) je zapisovatelný, tudíž kód může měnit všechny atributy - hodnoty uvnitř tohoto modulu. Obecně je možné zapisovatelný atribut také smazat příkazem del.
Protože každé jméno proměnné je součástí nějakého prostoru jmen, používá Python tzv. obory jmen. Každému oboru jmen je přiřazem jmenný prostor a všechna jména z tohoto prostoru jmen je možné díky tomu, že jsou součástí oboru jmen, používat bez kvalifikace, tj. bez uvedení prostoru jmen. I když to zní poněkud krkolomně, je za tím skryto logické chování všech proměnných. Pokud programátor někde napíše jméno promenna, aniž by uvedl, že se jedná o atribut nějakého objektu, např:
>>> vysledek = 3 * promenna
začne interpretr prohledávat všechny obory jmen v přesně definovaném pořadí. Poněvadž obor jmen není nic jiného než jmenný prostor, může obsahovat proměnné. Pokud první obor jmen hledanou proměnnou neobsahuje, je prohledáván druhý obor jmen a pak další atd.
V počátcích jazyka Python existovaly přesně vymezené tři obory jmen: lokální, globální a interních jmen. Jim přiřazené prostory jmen se liší v závislosti na právě prováděném kódu. Tak třeba při vyvolání funkce obsahuje lokální obor jmen lokální jmenný prostor funkce, čili obsahuje lokální proměnné, globální obor jmen pak odpovídá jmennému prostoru modulu, který danou funkci obsahuje, čili definuje globální proměnné. Interní obor jmen vždy obsahuje jmenný prostor modulu __builtin__, který definuje interní funkce (např. abs() nebo file()).
Každé přiřazení vytváří novou proměnnou v lokálním oboru jmen, uvnitř funkce tedy každé přiřazení vytváří novou lokální proměnnou. Zde si všimněte jisté asymetrie, přestože Python umožňuje používání jak lokálních, tak globálních a interních jmen zároveň, každé přiřazení vytvoří pouze lokální proměnnou, nelze proto přiřadit hodnotu globální proměnné nebo dokonce interní.
K tomu je v Pythonu příkaz global9.3, jenž umožňuje specifikovat, že tato proměnná je globální a tudíž i každé přiřazení této proměnné změní hodnotu odpovídající proměnné v globálním oboru jmen:
>>> def f(): ... global x ... x = 1 ... >>> x Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'x' is not defined >>> f() >>> x 1
Vše co jsme si řekli o přiřazení platí i o jiných příkazech, které vytvářejí nová jména proměnných. Například jde o příkaz import nebo o příkazy vytvářející nové pojmenované objekty jako def nebo class. Obdobně se chová i příkaz pro odstranění jména proměnné del.
V současnosti Python používá novou architekturu oborů jmen, kdy ke standardním třem oborů přibyl čtvrtý, který je umístěn mezi lokální a globální. Jde o tzv. nadřízený obor jmen. Představte si následující situaci: máme funkci f(), která ve svém těle definuje funkci g(). Chceme-li nyní, aby funkce g() používala proměnné z nadřízené funkce f(), budeme k nim přistupovat zrovna přes nadřízený obor jmen. Jde tudíž o obor jmen obsahující prostory jmen volajících funkcí.
Abychom mohli používat své vlastní objekty (v mluvě objektově orientovaných jazyků se jim říká instance tříd) musíme si nejprve nadefinovat třídu, z níž lze poté jednoduchým způsobem vytvořit instanci.
V některých jazycích se třída chová podobně jako datový typ, v Pythonu však třída je klasický objekt, stejně se s ní pracuje a všechno co platí pro objekty platí i pro třídy.
Co je pro třídy ale obzvláště důležité - dávají všem instancím stejné vlastnosti. V praxi to znamená, že např. metodu si nadefinujeme uvnitř třídy a automaticky jí získají i všechny instance této třídy. Názorně si tuto situaci můžete představit třeba takto: máme obecnou třídu auto, která definuje společná vlastnosti, třeba metodu jezdi() nebo tankuj(). Ve skutečnosti je ale třída auto jakási automobilka, teprve její instance jsou skutečná auta. Automobilka pouze určí, jak se budou auta chovat (to odpovídá implementování metod) a až samotná auto budou toto chování vykonávat (což neznamená nic jiného zavolání metody na nějaké instanci).
Třída je tedy jakási šablona určující chování všem jejím instancím. Proto lze změnou definice třídy dosáhnou změny chování všech její instancí - objektů.
Python pro definici třídy zavádí nový příkaz class, který vytvoří novou třídu. Příkaz class se chová podobně jako definice funkce, která vytvoří funkci z příkazů, které obsahuje její tělo. Obdobně i příkaz pro vytvoření třídy, který vytvoří novou třídy se zadaným jménem, jejíž chování (metody, atributy apod.) budou určovat příkazy v jejím těle:
class Jméno_třídy: <příkaz-1> . . . <příkaz-N>
Podobně jako u funkcí, i u tříd musí být před prvním použití třídy tato třída zadefinována. Jinými slovy, musí být spuštěna její definice. Z toho nám plynou některé užitečné vlastnosti Pythonu, například definici třídy můžeme umístit do větve příkazu if a podle nějaké podmínky se můžeme rozhodnout, jak třídu definujeme.
Uvnitř těla definice třídy jsou specifické příkazy, kterými se definuje chování třídy. Většinou se jedná o definice funkcí, uvnitř těla třídy se ale příkaz def chová poněkud odlišněji od standardního příkazu definice funkce, tak jak ho známe z předchozího povídání. Nenabývejte ale dojmu, že zde není povoleno nic více než definice metod, Python dovoluje do těla třídy umístit libovolné příkazy, které budou vykonány při spuštění definice této třídy.
Při vykonání definice třídy je vytvořen nový prostor jmen, který je použit jako lokální obor jmen uvnitř těla třídy. Z vlastností oborů jmen je nám již jasné, že všechna jména proměnných, která uvnitř těla třídy vytvoříme budou umístěna v prostoru jmen této třídy.
Pokud se definice třídy provede normálně (tj. nedojde v těle k žádné výjice), pak je vytvořen objekt třídy, čili Pythonový objekt reprezentující tuto třídu. Objekt třídy je uložen pod jménem, které bylo zadáno při definici funkce. Tento objekt, kromě jiného, vytváří jakýsi obal nad vytvořeným prostorem jmen. Po vytvoření objektu třídy je obnoven lokální obor jmen, který je nastaven zpět tak, aby obsahoval stejný obor jmen jako před spuštěním definice funkce.
Třídní objekty obsahují, jak již bylo řečeno, prostor jmen uchovávající objekty, které byly definovány v těle funkce. Kromě toho ale podporuje i další dva druhy operací - zpřístupnění atributů a vytvoření instance.
Nejprve se budeme věnovat atributům třídy. Jak jsme si řekli hned na začátku této kapitoly, každý objekt může mít mnoho atributů a ani třídy nejsou výjimkou. Jména atributů odpovídají jménům ve jmenném prostoru třídy. Pokud tedy budeme uvažovat třídu MojeTřída, která je definována takto:
class MojeTřída: "Jednoduchá třída" i = 12345 def f(self): return 'ahoj'
pak MojeTřída.i
a MojeTřída.f
jsou atributy třídy
MojeTřída. První z nich odkazuje na objekt čísla 12345 a druhý na
funkci f. Všechny atributy třídy jsou jak pro čtení, tak je do nich
umožněn i zápis.
Všimněte si řetězce, který je uveden na druhém řádku definice třídy. Jde o dokumentační řetězec, který funguje úplně stejným způsobem jako dokumentační řetězec nějaké funkce. Tento řetězec je možné zpřístupnit jako atribut __doc__.
Vytváření instancí používá zápis funkčního volání. Nenechte se však zmýlit, operátor volání funkce a vytvoření instance má úplně jiný význam a funkci. Pokud použijeme třídu MojeTřída a její definici, kterou jsme si uvedli výše, pak instanci této třídy můžeme vytvořit pomocí příkazu:
x = MojeTřída()
Nyní již bude jméno x
odkazovat na instanci třídy. Tato instance,
jinými slovy též instanční objekt je prázdná, nemá nastaveny žádné
atributy. Pokud bychom tento objekt chtěli hned po vytvoření nastavit do
nějakého stavu, jinými slovy nastavit nějaké jeho atributy, můžeme definovat
speciální metodu s názvem __init__(). Prozatím bych poprosil čtenáře
o trochu shovívavosti, pojem metoda si rozvedeme dále v této kapitole.
def __init__(self): self.data = []
Metoda (funkce) __init__() je zavolána při vytvoření instance třídy, přičemž za argument self je dosazena tato instance. Novou ziniciovanou instanci získáme opět pomocí objektu třídy:
x = MojeTřída()
Metoda __init__() nám nabízí i další možnost - definovat ji s více argumenty, pak budou tyto argumenty vyžadovány při vytvoření instance, budeme je tedy muset předat objektu třídy, který následně zavolá metodu __init__() a všechny argumenty jí předá:
>>> class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>> x = Complex(3.0, -4.5) >>> x.r, x.i (3.0, -4.5)
Již máme vytvořeny instance třídy. Škála operací, kterou nám instance nabízí je ale ještě užší než umožňují třídy, jde o pouhé zpřístupnění atributů. Jazyk Python rozlišuje dva druhy atributů, jednak jde o datové atributy a pak o tzv. metody.
Datové atributy se nazývají v terminologii různých objektových jazyků různě,
např. Smalltalk je nazývá "instanční proměnné" a C++ "datové členy". Datové
atributy vznikají podobně jako proměnné - při definici, tj. při prním
přiřazení nějaké hodnoty jejich jménu. Pokud budeme pokračovat v předchozím
příkladu s třídou MojeTřída a budeme předpokládat, že x je jméno
odkazující na instanci této třídy, pak následující fragment kódu vytiskne
hodnotu 16
:
x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print x.counter del x.counter
Kromě datových atributů rozlišuje Python ještě metody. Již několikrát v této publikaci zaznělo, že metoda je funkce patřící k určitému objektu. O pár řádků výše jsme si řekli, že i instance jsou objekty a proto třídy umožňují definování metod, které se chovají naprosto stejně jako metody jiných objektů (např. seznamů).
Jména metod se specifikují v definici třídy. Python při rozlišování mezi datovými atributy a metodami postupuje velice jednoduše. Pokud je nějaký objekt v těle třídy funkce, pak jde o metodu, jinak jde o datový atribut.
Pokud se opět zaměříme na příklad s instancí x
třídy MojeTřída,
potom x.f
je metoda, protože třída MojeTřída definuje funkci
f, oproti tomu x.i
je datový atribut, jelikož tělo třídy
definuje i
jako číslo 12345
.
Dejte si pozor na následující: Zatímco x.f je metoda f
instance x
, MojeTřída.f je funkce. Zapamatujte si proto, že o
metodu se jedná pouze v případě, kdy k této metodě existuje i instance. Funkce
MojeTřída.f je pouze jakýsi prototyp metody x.f, metoda
z ní vznikne spojením s instancí.
Každá metoda se chová stejně jako funkce (jediný rozdíl oproti funkci je její provázanost s instancí), můžeme jí tedy volat jako funkci:
x.f()
Protože MojeTřída.f kdykoli vrací řetězec 'ahoj'
, pak i tato
metoda vrátí 'ahoj'
. Metodu ale nemusíme volat přímo, můžeme jí uložit
do nějaké proměnné a zavolat později. Zde si, prosím, uvědomte, že instance,
pro kterou bude metoda volána se neurčuje podle objektu "před tečkou",
ale je pevně zapsán v té které metodě. Proto je možné metody uchovávat pod
jinými jmény a vůbec, pracovat s nimi jakoby to byly obyčejné funkce, což
nejlépe demonstruje následující příklad:
xf = x.f while 1: print xf()
Podívejme se tedy na to, co se stane, je-li metoda zavolána. Protože Python provádí roztřídění mezi datovými atributy a metodami ihned při vytvoření instance, má již v tuto chvíli "jasno" ohledně toho, že je 100% volána metoda.
Každá metoda je složena z odpovídající funkce a instance třídy. Běhové
prostředí Pythonu proto z metody tyto dvě vlastnosti získá a následně zavolá
právě vyextrahovanou funkci s těmito atributy: první bude instance (je vám
již jasno, proč každá metoda má první argument self
?) a pak následují
ty atributy, které byly předány při volání metody.
Proto je nutné každou metodu definovat s jedním argumentem "navíc". Za tento atribut se pak při volání metody dosadí instance, pro kterou byla metoda zavolána. A co víc, pomocí tohoto argumentu se odkazujeme na datové atributy instance. Instancí totiž může být vytvořeno mnoho, ale při běhu metody díky výše popsanému mechanismu máme jistotu, že vždy používáme tu správnou instanci (tj. tu, pro níž jsme metodu volali).
Python dokonce umožňuje toto rozložení na instanci a funkci nechat na programátorovi, který může tuto funkci zavolat a místo prvního argumentu jí předat rovnou instanci:
x.f() MojeTřída.f(x)
Tyto dva zápisy jsou ekvivalentní. Dobře si je zapamatujte, protože v některých situacích musíte metody volat takto explicitně, protože chcete použít jinou funkci, než kterou by použil interpretr.
Poznamenejme, že jako první argument můžete funkci, která de facto reprezentuje metodu předat pouze instanci její třídy. Pokud se pokusíte o jakýkoli jiný postup, nekompromisně dojde k výjimce:
MojeTřída.f('fooo') Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: unbound method f() must be called with instance as first argument
Nyní následují náhodné poznámky, které se (převážně) týkají problematiky objektově orientovaného programování v jazyce Python.
Poněvadž datové atributy a metody sídlí ve stejném jmenném prostoru, dojde při přiřazení nějaké hodnoty nějakému jménu uvnitř těla promněnné k přepsání metody, která měla stejné jméno. To nejlépe demostruje následující fragment kódu:
class Trida: def foo(self, x): return x + 1 foo = 'novy text'
Pak již atribut instance třídy Trida neobsahují metodu foo, nýbrž datový atribut třídy Trida.foo. Proto je vhodné používat určitou konvenci pro pojmenovávání datových členů a metod. Podle jedné z nich můžete např. datové atributy psát s malým a metody s velkým počátečním písmenem. Další možností je pojmenovávat datové atributy podstatnými jmény a metody slovesy. Podobných konvencí existuje mnoho a jistě si brzy osvojíte tu "svoji".
Datové atributy nějaké instance jsou přístupné jak metodám této instance, tak i jakémukoli jejímu uživateli (klientovi). Python nepodporuje modifikátory atributů, kterými by bylo možné určité datové atributy prohlásit za soukromé. Je možné používat pouze pseudo-soukromé atributy o nichž si budeme povídat dále v této kapitole.
Z předchozího odstavce plyne i nemožnost vytvoření čistě abstraktních datových typů v jazyce Python, protože žádná třída nedokáže skrýt svoje atributy před zraky ostatních objektů. Pokud však tuto třídu přepíšete do C, implementační detaily se skryjí a používá se pouze rozhraní této třídy. V jazyce C se ale programuje velice nepohodlně (v porovnání s Pythonem) a tudíž se tento krok nedoporučuje.
Přestože všechny datové atributy instancí jsou veřejné, měli by je klientské objekty používat s obezřetností. Klient totiž může porušit složité závislosti mezi daty uvnitř instance a metody instance by pak nemusely pracovat správně. Pokud ovšem víte, že váš přístup nenaruší konzistenci instančních dat, pak vám nikdo nebrání tyto datové atributy modifikovat a dosáhnout tak požadovaného chování objektů. Klienti instance dokonce mohou do jejího jmenného prostoru ukládat své vlastní atributy, čímž lze např. označkovat určité instance apod. Jediným předpokladem je, že použijete unikátní jméno atributu. Tím se vyhnete kolizi s datovými atributy instance.
Jak jsme si již jednou řekli, v Pythonu neexistuje žádná zkratka, kterou se
lze odkazovat na datové atributy, případně jiné metody z metody této instance.
Vždy musíte tyto atributy a metody kvalifikovat, což neznamená nic jiného,
než před ně uvést jméno self
a tečku. Toto opatření vysoce zvyšuje
čitelnost kódu, i při letmém pohledu na zdrojový kód nelze zaměnit lokální
a instanční proměnnou.
Podle ustálených konvencí je první argument metod vždy pojmenován self
.
Přestože jde pouze o konvenci a nikdo vám nebrání v používání vlastního jména,
nedoporučuje se to. Kdykoli, kdy tuto konvenci porušíte, přinejmenším zmatete
čtenáře zdrojového kódu. Některé programy, které umožňují interaktivně
procházet datovými strukturami a objekty definovanými v prostředí interpretru,
mohou být také zmateny.
Každé jméno uložené ve jmenném prostoru třídy a odkazující na funkci, znamená zavedení nové metody pro instance této třídy. Přitom není nutné, aby tato funkce byla přímo uložena v těle definice třídy. Lze jí například definovat před třídou samotnou a v těle třídy pouze tento funkční objekt přiřadit nějakému jménu:
# Funkce definovaná mimo třídu def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'ahoj' h = g
Po provedení těchto definici jsou f
, g
a h
atributy třídy
C, odkazují však na jedinou funkci f1. Zároveň jsou
f
, g
a h
jména metod instancí třídy C. Každá
z těchto metod však má stejnou implementaci -- funkci f1. Těmto
praktikám se pokud možno vyhněte, protože dokáží dokonale zmást, v některých
případech je ale jejich použití ospravedlnitelné zjednodušením kódu případně
zvýšením funkčnosti výsledného programu.
Jak již bylo několikrát řečeno, jedna metoda může volat druhou pomocí
argumentu self
. Ten reprezentuje instanci pro níž byla metoda zavolána
a tudíž obsahuje všechny jeho metody:
class Bagl: def __init__(self): self.data = [] def pridej(self, x): self.data.append(x) def pridej_dvakrat(self, x): self.pridej(x) self.pridej(x)
Protože kromě speciální chování metod, které umožňuje jejich provázání s instancemi jde o obyčejné funkce, lze z nich používat i globální proměnné a další vymoženosti funkcí jako jsou argumenty pro proměnný počet parametrů apod. Podobně jako u funkcí, i metody mají jako svůj globální obor jmen jmenný prostor modulu, jež obsahuje definici třídy (zde pozor na častý omyl: jmenný prostor třídy nikdy není globálním oborem jmen!).
Jedna ze tří základních vlastností objektově orientovaného programování se nazývá dědičnost. Protože tato publikace nechce být učebnicí programování, ale pouze průvodcem jazykem Python, nebudeme zde zacházet do detailů, to ponecháme fundovaným učebnicím objektově orientovaného programování.
Proto si pouze řekneme, co nám dědičnost umožňuje. Jedná se vlastně o odvození nové třídy z nějaké původní, přičemž nová a původní třída jsou v určitém vztahu a předpokládá se, že odvozená třída definuje všechny metody svého předka a ještě může definovat některé metody navíc. Tyto nově definované metody mohou rozšiřovat nebo dokonce úplně předefinovávat chování předka.
Pokud tedy budeme uvažovat třídu Bagl ze závěru předchozí části této kapitoly a budeme jí chtít rozšířit tak, aby při přidání nějakého nového prvku vytiskla ještě hlášení o tom, jaký prvek přidává, použijeme následující definici třídy:
class Bagl_s_tiskem(Bagl): def __init__(self): print 'iniciuji bagl' Bagl.__init__(self) def pridej(self, x): print 'pridavam prvek %s' % x Bagl.pridej(self, x) def pridej_dvakrat(self, x): print 'pridavam dvakrat:' Bagl.pridej_dvakrat(self, x) def pridej_trikrat(self, x): print 'pridavam trikrat:' self.pridej_dvakrat(x) self.pridej(x)
Tím jsme definovali novou třídu, která je odvozená od třídy Bagl (viz hlavička definice). V této třídě jsme předefinovali chování původních metod tak, že nejprve vytisknou hlášení a poté zavolají metodu tak, jak byla definována v předkovi (viz zápis pomocí atributu nadřízené třídy "Bagl.pridej(self, x)").
Velice důležitou vlastností, kterou by mělo podporovat také každé objektově orientované prostředí je polymorfismus. V Pythonu je jeho podpora samozřejmostí. Proto si všimněte metody Bagl_s_tiskem.pridej_dvakrat(). Ta nejprve vytiskne klasické hlášení a posléze zavolá metodu svého předka. Nyní se podívejte na to, jak je definována metoda Bagl.pridej_dvakrat() -- dvakrát zavolá metodu pridej(). Tato metoda je ovšem hledána v instanci, kde se jako první narazí na metodu Bagl_s_tiskem.pridej(). Tím vlastně původní kód vyvolá a použije naší novou metodu. Podařilo se nám ji dokonale předefinovat.
Hledání atributů (je jedno jestli datových atributů nebo metod) instance se děje velice jednoduchým způsobem. Nejprve se prohledá třída této instance, pokud je atribut definován zde, končíme a použije se jeho hodnota. Pokud se zde atribut nenajde, zkusí Python prohledat předky této třídy, pokud nějací existují. Existuje totiž možnost, že nějaká metoda, kterou v potomkovi nechceme předefinovat, bude definována v předkovi. A protože předek opět může být potomkem nějakého prapředka, zkusí se atribut najít v případě neúspěchu i v tomto prapředkovi. Pokud se dospěje ke kořeni stromu tříd, ale žádný odpovídající atribut není nalezen, dojde k výjimce AttributeError.
Protože metoda definovaná v potomkovi většinou pouze rozšiřuje funkčnost
původní metody, je často nutné volat metodu předka. Python nepodporuje přímé
vyvolání metody předka, tudíž musíte použít druhou možnost volání metody, který
využívá funkčního objektu odpovídajícího metodě. V potomkovi proto zavoláme
tuto funkci definovanou v předkovi a jako argument mu předáme argument
self
a další argumenty. Takto nemusíme volat přímo metodu přímého
předka, postup lze použít i na prapředky apod.
Naše vzorová odvozená třída dokonce rozšiřuje paletu metod svých instancí, protože definuje metodu pridej_trikrat(), která ke své implementaci využívá metody pridej() a pridej_dvakrat(). Obecně lze říci, že instance zděděné třídy je svojí funkčností nadmnožinou instance svého předka, což znamená, že definuje vše co předek a ještě nějaké metody nebo datové atributy navíc. Proto lze většinou instance potomka používat na místech, kde jsou očekávány instance předka.
Je-li nějaký objekt instancí určité třídy nám umožňuje zjistit interní funkce isinstance(), které jako první argument předáme objekt a druhý argument třídu. Funkce pak vrátí 1, jestliže objekt je instancí třídy, 0 je-li tomu jinak. Zároveň platí, že instance nějaké odvozené třídy je vždy i instancí rodičovské třídy:
>>> batoh1 = Bagl() >>> batoh2 = Bagl_s_tiskem() >>> isinstance(batoh1, Bagl) 1 >>> isinstance(batoh2, Bagl) 1 >>> isinstance(batoh1, Bagl_s_tiskem) 0 >>> isinstance(batoh2, Bagl_s_tiskem) 1
V Pythonu je samozřejmostí vícenásobná dědičnost. Třídu s více předky definujeme podobně jako třídu s jedním rodičem:
class OdvozenáTřída(Rodič1, Rodič2, Rodič3): <příkaz-1> . . . <příkaz-N>
Základní a jediné pravidlo, které musíte uvažovat při práci s instancemi tříd, které mají více předků je "nejprve celý strom, až pak další třídy zleva doprava". Tato magická věta nám dáva tušení o tom, jak funguje vyhledávání atributů u tříd s více předky. Pokud totiž atribut není nalezen ve třídě této instance, hledá se u jejích předků. Výše uvedené pravidlo pak určuje pořadí v jakém jsou prohledávány rodičovské třídy. První část věty znamená jednoduše, že nejprve Python bude prohledávat první rodičovskou třídy (v našem případě Rodič1) a její předky. Až pokud daný atribut není nalezen (může jím být jak datový člen tak metoda), prohledává se druhá rodičovská třída (Rodič2) a její předci. A v případě selhání i zde se pokračuje poslední rodičovskou třídou (Rodič3) a jejími předky. Může nastat situace, kdy daný atribut není nalezen ani v této třídě, pak ale dojde k výjimce AttributeError a běh programu se přeruší.
Součástí každé učebnice, která se alespoň okrajově zmiňuje o objektově orientovaném programování v libovolném jazyce, jenž podporuje vícenásobnou dědičnost, je téměř vždy varování před zneužíváním tohoto mechanismu. Ani naše publikace v tomto směru nebude výjimkou. Nadměrné používání vícenásobné dědičnosti vede dříve nebo později ke zmatení jak čtenářů kódu, tak samotného programátora. Platí zde více než kdekoli jinde nutnost dodržování předem daných konvencí. Jedině tak lze udržet kód přehledný a snadno udržovatelný. Dalším známým problémem je tzv. diamantová struktura tříd, jak je nazýván případ, kdy od jednoho předka jsou odvozeny dvě třídy a od těchto dvou tříd zároveň je odvozena třetí. V tomto případě si s mechanismy zde popsanými těžko pomůžete a na řadu musí přijít nějaká jiná, sofistikovanější metoda.
Jak již několikráte zaznělo v textu výše, Python podporuje tzv. pseudo-soukromé
atributy. Jako pseudo-soukromý atribut je používán každý identifikátor ve
tvaru __identifikátor
. Tento speciální zápis je tvořen jménem
identifikátoru, jemuž předcházejí minimálně dvě podtržítka a je ukončeno
nejvýše jedním podtržítkem. Každý výskyt takového identifikátoru je nahrazeno
novým identifikátorem _jménotřídy_identifikátor
, kde jménotřídy
je jméno aktuální třídy. Jako aktuální třída se použije třída, v jejímž kódu
(tj. kódu její metody) se takovýto zápis vyskytl. Takto lze zajistit, že dvě
různé třídy mohou definovat dva stejné soukromé atributy a přesto nedojde ke
kolizi identifikátorů. Pomocí pseudo-soukromých atributů můžeme pojmenovávat
jak datové členy, tak i metody. Co je však velmi důležité - tento mechanismus
můžeme použít i pro uložení určitých informací instance jedné třídy uvnitř
instance druhé. Nakonec poznamenejme, že mimo kód třídy stejně jako v případě,
kdy jméno třídy je tvořeno samými podtržítky, k žádnému nahrazování nedojde.
Název pseudo-soukromé atributy se používá proto, že přestože dojde ke změně
jména, je toto jméno stále přístupné jako _jménotřídy_identifikátor
a to
pro úplně libovolný kód. Toho můžete využít například v případě, kdy chcete
při ladění programu zkontrolovat hodnotu určité proměnné.9.4
Je třeba podotknout, že kód předaný příkazu exec
a funkcím pro dynamické
vykonávání kódu nepovažuje jméno volající třídy za jméno aktuální třídy a tudíž
se neprovádí rozšiřování identifikátorů. S obdobným problémem se potkáte
v případě používání funkcí getattr(), setattr() a
delattr(), podobně se chová i přístup ke jmennému prostoru
nějaké instance pomocí atributu __dict__. V těchto případech vždy
musíte provést nahrazení ručně!
Někdy se programátorovi může hodit datový typ podobný pascalovskému záznamu nebo strukturám v jazyce C. Tyto typy obsahují několik datových členů k nimž se přistupuje na základě jejich jmen. Tento typ získáme jednoduše definováním prázdné třídy:
class Zamestnanec: pass honza = Zamestnanec() # Vytvoří prázdný záznam o zaměstnanci # Vyplní jednotlivé položky záznamu honza.jmeno = 'Honza Švec' honza.skupina = 'PyCZ' honza.plat = 0
Kód, který předpokládá, že mu předáte určitý abstraktní datový typ (tj. typ u nějž je známo pouze rozhraní a nezáleží na jeho implementaci) lze často "ošálit" a předat mu instanci jiné třídy, která však implementuje požadované metody. Například si představte funkci, které předáte souborový objekt, ona pomocí jeho metody read() přečte jeho obsah a vrátí ho jako řetězec, v němž nahradí všechny výskyty tabulátoru čtyřmi mezerami. Pak místo souborového objektu můžeme předat i libovolnou jinou třídu, která má metodu read() definovánu stejným způsobem jako souborový objekt.
Objekt metody, která je svázána s určitou instancí má také svoje vlastní datové
atributy. Jsou jimi im_self
, který reprezentuje instanci, na níž se
metoda váže a im_func
reprezentující funkční objekt, který metodu
implementuje. Jde o funkci, která je definována v těle třídy a která se vyvolá
při spuštění metody.
Uživatelsky definované výjimky již v Pythonu dávno nejsou omezeny pouze na řetězce. Nesrovnatelně lépe totiž mohou posloužit třídy. Díky nim můžeme vytvořit rozsáhlou hiearchii výjimek, ve které se mezi třídami dědí atributy a jejich případné metody apod.
Kvůli možnosti používání tříd byl rozšířen i příkaz raise. Jeho syntaxe nyní vypadá následovně:
raise třída, instance raise instance
V prvním případě musí být proměnná instance
instancí třídy třída
nebo třídy, která je od ní odvozená. Druhý tvar je pak zkráceninou zápisu:
raise instance.__class__, instance
Větve except příkazu try mohou uvádět třídy stejným způsobem jako řetězce. Daný handler se spustí, pokud výjimka je instancí jedné ze tříd uvedených za příslušným klíčovým slovem except. Pro ilustraci můžeme uvést následující vyumělkovaný příklad. Ten vytiskne písmena "B", "C" a "D" přesně v tomto pořadí:
class B: pass class C(B): pass class D(C): pass for c in [B, C, D]: try: raise c() except D: print "D" except C: print "C" except B: print "B"
Pokud bychom větve except zapsali v opačném pořadí (tj. "except B" jako první), vytisklo by se "B", "B", "B", protože vždy by se spustil pouze první handler!
Pokud výjimka nebude odchycena a byde prohlášena za neobslouženou výjimku, bude jako popis chyby nejprve uvedeno jméno třídy následované dvojtečkou a řetězcovou reprezentací třídy, kterou vrátí interní funkce str().