NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

Lekce 10 - Dědičnost a polymorfismus v Pythonu

V minulé lekci, Aréna s bojovníky v Pythonu, jsme dokončili naši arénu simulující zápas dvou bojovníků.

V následujícím tutoriálu si opět rozšíříme znalosti o objektově orientovaném programování v Pythonu. V úvodní lekci do OOP jsme si říkali, že OOP stojí na třech základních pilířích: zapouzdření, dědičnosti a polymorfismu. Zapouzdření a používání podtržítek už dobře známe. Dnes se podíváme na zbylé dva pilíře.

Dědičnost

Dědičnost je jedna ze základních vlastností OOP a slouží k tvoření nových datových struktur na základě starých. Vysvětleme si to na jednoduchém příkladu:

Budeme programovat informační systém. To je docela reálný příklad. Abychom si však učení zpříjemnili, bude to informační systém pro správu zvířat v ZOO :-) Náš systém budou používat dva typy uživatelů: uživatel a administrátor. Uživatel je běžný ošetřovatel zvířat, který bude moci upravovat informace o zvířatech, např. jejich váhu nebo rozpětí křídel. Administrátor bude moci také upravovat údaje o zvířatech a navíc zvířata přidávat a mazat z databáze. Z atributů bude mít navíc telefonní číslo, aby ho bylo možné kontaktovat v případě výpadku systému. Bylo by jistě zbytečné a nepřehledné, kdybychom si museli definovat obě třídy úplně celé, protože mnoho vlastností těchto dvou objektů je společných. Uživatel i administrátor budou mít jistě jméno, věk a budou se moci přihlásit a odhlásit. Nadefinujeme si tedy pouze třídu Uzivatel (nepůjde o funkční ukázku, dnes to bude jen teorie, programovat budeme příště):

class Uzivatel:

    def __init__(self, jmeno, heslo, vek):
        self.__jmeno = jmeno
        self.__heslo = heslo
        self.__vek = vek

    def prihlasit(self, heslo):
    ...

    def odhlasit(self):
    ...

    def nastav_vahu(self, zvire):
    ...

    ...

Třídu jsme si jen naznačili, ale jistě si ji dokážeme dobře představit. Bez znalosti dědičnosti bychom třídu Administrator definovali takto:

class Administrator:

    def __init__(self, jmeno, heslo, vek, telefonni_cislo):
    self.__jmeno = jmeno
    self.__heslo = heslo
    self.__vek = vek
    self.__telefonni_cislo = telefonni_cislo

    def prihlasit(self, heslo):
    ...

    def odhlasit(self):
    ...

    def nastav_vahu(self, zvire):
    ...

    def pridej_zvire(self, zvire):
    ...

    def vymaz_zvire(self, zvire):
    ...

    ...

Vidíme, že máme ve třídě spoustu redundantního (duplikovaného) kódu. Jakékoli změny musíme nyní provádět v obou třídách, kód se nám velmi komplikuje. Řešením tohoto problému je dědičnost. Definujeme třídu Administrator tak, aby z třídy Uzivatel dědila. Atributy a metody uživatele tedy již nemusíme znovu definovat, Python je sám do třídy dodá:

class Administrator(Uzivatel):

    def __init__(self, jmeno, heslo, vek, telefonni_cislo):
        super().__init__(jmeno, heslo, vek)
        self.__telefonni_cislo = telefonni_cislo

    def pridej_zvire(self, zvire):
    ...

    def vymaz_zvire(self, zvire):
    ...

    ...

Vidíme, že ke zdědění používáme závorky. Mezi závorky píšeme třídy, od kterých naše třída dědí. Syntaxe je tedy class TridaPotomka(TridaRodice):. V anglické literatuře se dědičnost označuje slovem inheritance.

Toho "podivného" super() si zatím nebudeme všímat - bude vysvětleno později (ale je nutné, pokud chceme použít metodu rodiče). Vraťme se zpět k příkladu.

V potomkovi nebudou přístupné privátní atributy rodiče (označené dvojitým podtržítkem). Přístupné budou pouze veřejné atributy a metody. Privátní atributy a metody jsou chápány jako speciální logika konkrétní třídy, která je potomkovi utajena. I když ji vlastně používá, nemůže ji měnit (s výjimkou přes name mangling, který jsme si vysvětlili v lekci Zapouzdření atributů podrobně).

Říkali jsme si, že přístup k privátním atributům tímto způsobem není považován za dobrou praxi, protože porušuje princip zapouzdření a může vést k nepředvídaným chybám nebo komplikacím. Neuškodí to zopakovat.

Abychom zpřístupnili vybrané atributy rodiče i jeho potomkovi, použijeme jako modifikátor přístupu jedno podtržítko. V Pythonu se atributy a metody s jedním podtržítkem nazývají vnitřní. My už víme, že pro ostatní programátory nebo objekty to znamená: "Toto je sice zvenčí viditelné, ale, prosím, neměňte mi to!" Začátek třídy Uzivatel tedy bude vypadat takto:

class Uzivatel:

    def __init__(self, jmeno, heslo, vek):
        self._jmeno = jmeno
        self._heslo = heslo
        self._vek = vek

Když si nyní vytvoříme instance uživatele a administrátora, oba budou mít např. atribut jmeno a metodu prihlasit(). Python třídu Uzivatel zdědí a automaticky nám doplní všechny její atributy.

Výhody dědění jsou jasné. Nemusíme opisovat oběma třídám ty samé atributy. Stačí dopsat jen to, v čem se liší. Zbytek se podědí. Přínos je obrovský, můžeme rozšiřovat existující komponenty o nové metody a tím je znovu využívat. Nemusíme psát spousty redundantního (duplikovaného) kódu. A hlavně - když změníme jediný atribut v mateřské třídě, tato změna všude automaticky podědí. Nedojde tedy k tomu, že bychom to museli měnit ručně u dvaceti tříd a někde na to zapomněli a způsobili chybu. Jsme lidé a chybovat budeme vždy, musíme tedy používat takové programátorské postupy, abychom měli možností chybovat co nejméně.

O mateřské třídě se obvykle hovoří jako o předkovi (zde Uzivatel) a o třídě, která z ní dědí, jako o potomkovi (zde Administrator). Potomek může přidávat nové metody nebo si uzpůsobovat metody z mateřské třídy (viz dále).

Kromě uvedeného názvosloví se často setkáme i s pojmy nadtřída a podtřída.

Další možností, jak objektový model navrhnout, je zavést mateřskou třídu Uzivatel, která by sloužila pouze k dědění. Z třídy Uzivatel bychom potom dědili třídu Osetrovatel a z ní třídu Administrator. Taková struktura se však vyplatí až při větším počtu typů uživatelů. Hovoříme zde o hierarchii tříd. Náš příklad byl jednoduchý a proto nám stačily pouze dvě třídy. Existují tzv. návrhové vzory, které obsahují osvědčená schémata objektových struktur pro známé případy užití. Máme je popsané v sekci Návrhové vzory, je to však již pokročilejší problematika a také velmi zajímavá. V objektovém modelování se dědičnost znázorňuje graficky jako prázdná šipka směřující k předkovi. V našem případě grafická notace vypadá takto:

Dědičnost objektů – grafická notace - Objektově orientované programování v Pythonu

Jazyky, které dědičnost podporují, buď umí dědičnost jednoduchou, kde třída dědí jen z jedné třídy, nebo vícenásobnou, kde třída dědí hned z několika tříd najednou. Python podporuje vícenásobnou dědičnost jako např. C++.

Všechny objekty v Pythonu dědí ze třídy object.

Testování typu třídy

Testování, zda je objekt instancí určité třídy, je v Pythonu užitečné z několika důvodů:

  • typová kontrola: Zejména v dynamicky typovaných jazycích jako je Python je často důležité zkontrolovat, zda jsou proměnná nebo objekt určitého typu (třídy), než s nimi provádíme další operace. Pomůže nám to zabránit chybám v kódu.
  • přizpůsobení chování: Testování typu nám umožňuje, aby náš kód reagoval jinak na základě toho, jakého typu je objekt. Například máme funkci, která přijímá různé třídy objektů a každý z nich má být zpracován trochu jinak. Zde využijeme kontrolu typu k rozhodnutí, jaký kód bude vykonán.

Testování pomocí funkce type()

Funkce type() se v Pythonu běžně používá k získání přímého typu objektu. Výsledek této funkce je obvykle užitečný pro jednoduché datové typy:

if type(x) == list:
    print("x je seznam")

if type(y) == str:
    print("y je řetězec")

a = 10
print(type(a) == int)  # Výsledek: True

y = "Hello"
print(type(y) == int)

Ve výstupu konzole uvidíme:

Výstup funkce type():
x je seznam
y je řetězec
True
False

Testování pomocí funkce isinstance()

Preferovaným způsobem ověření, zda je objekt instancí určité třídy nebo některého z jejích potomků, je však vestavěná funkce isinstance().

Důvodem je to, že funkce isinstance() bere v úvahu dědičnost, zatímco funkce type() to nedělá. Pokud máme třídu, která dědí z jiné třídy, type() nám vrátí pouze přesnou třídu objektu, zatímco isinstance() potvrdí, zda je objekt instancí některé třídy v hierarchii dědičnosti. Podívejme se na příklad:

class Rodic:
    pass

class Potomek(Rodic):
    pass

karel = Potomek()

print(type(karel) == Rodic)
print(isinstance(karel, Rodic))

Ve výstupu konzole uvidíme:

Výstup funkce isinstance():
False
True

V tomto příkladu je karel instancí třídy Potomek, ale díky dědičnosti je také instancí třídy Rodic. Funkce isinstance() to správně rozpozná, zatímco type() vrátí pouze konkrétní třídu potomka, nikoli třídu rodiče nebo jakoukoli jinou třídu v hierarchii dědičnosti.

Proto je pro komplexnější objekty a práci s třídami vhodnější použít funkci isinstance().

Polymorfismus

Nenechme se vystrašit příšerným názvem této techniky, protože je v jádru velmi jednoduchá. Polymorfismus umožňuje používat jednotné rozhraní pro práci s různými typy objektů. Mějme například mnoho objektů, které reprezentují nějaké geometrické útvary (kruh, čtverec, trojúhelník). Bylo by jistě přínosné a přehledné, kdybychom s nimi mohli komunikovat jednotně, ačkoli se liší. Mějme například třídu GeometrickyUtvar, která obsahuje atribut barva a metodu vykresli(). Všechny geometrické tvary potom budou z této třídy dědit její interface (rozhraní). Objekty kruh a ctverec se ale jistě vykreslují každý jinak. Polymorfismus nám proto umožňuje přepsat si metodu vykresli() u každé podtřídy tak, aby dělala, co chceme. Rozhraní tak zůstane zachováno a my nebudeme muset přemýšlet, jak se to u onoho objektu volá.

Polymorfismus bývá často vysvětlován na obrázku se zvířaty, která mají všechna v rozhraní metodu speak(), ale každé si ji vykonává po svém:

Polymorfismus - Objektově orientované programování v Pythonu

Podstatou polymorfismu je tedy metoda nebo metody, které mají všichni potomci definované se stejnou hlavičkou, ale jiným tělem. Detailně se touto problematikou budeme zabývat v lekci Abstraktní třídy v Pythonu.

V další lekci, Aréna s mágem (dědičnost a polymorfismus), si polymorfismus spolu s dědičností vyzkoušíme na bojovnících v naší aréně. Přidáme mága, který si bude metodu utoc() vykonávat po svém pomocí many, ale jinak zdědí chování a atributy bojovníka. Zvenčí tedy vůbec nepoznáme, že to není bojovník, protože bude mít stejné rozhraní. Bude to zábava :-)


 

Předchozí článek
Aréna s bojovníky v Pythonu
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Aréna s mágem (dědičnost a polymorfismus)
Článek pro vás napsal gcx11
Avatar
Uživatelské hodnocení:
485 hlasů
(^_^)
Aktivity