{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Co je to objektově-orientované programování?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pro objektové (...ě-orientové) programování (OOP) je zásadní koncept **objektu** -- ten představuje ucelenou kombinaci *dat* a *operací*, jež s nimi jdou dělat. Objekt obvykle (ne vždy) data schovává a navenek poskytuje jen několik přístupných operací, tzv. **metod**. Přitom typicky toho, kdo k objektu přistupuje, nezajímá implementace oněch metod (objekt klidně jejich vykonání může delegovat na jiné objekty), ani to, jakým způsobem jsou v objektu uspořádána data.\n", "\n", "K objektově-orientovanému programování patří i několik dalších konceptů, různé programovací jazyky si z nich berou jen některé, také je interpretují různě. Ukážeme si, které koncepty mají v Pythonu smysl a jak je využít.\n", "\n", "\n", "\n", "**Obvyklé koncepty:** zapouzdření, dědičnost, polymorfismus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Objekty" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "V Pythonu je všechno **objekt** (na rozdíl od C++, Javy). Objektem jsou všechny hodnoty vestavěných typů (čísla, řetězce, ...), všechny kontejnery, i funkce, moduly, i typy objektů. Úplně vše poskytuje nějaké veřejné metody." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Typ objektu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Každý objekt má nějaký **typ**, přičemž typy rozdělujeme na **vestavěné typy** (list, tuple, int, ...) a **třídy** (typy definované pomocí klíčového slova `class`). Typ určuje, jaké metody objekt nabízí, představuje jakousi šablonu (obecné vlastnosti), od které se pak individuální objekt liší vnitřním stavem (konkrétní vlastnosti). Říkáme, že objekt je **instancí** daného typu. Pro zjištění typu objektu zlouží vestavěná funkce `type`." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n", "\n" ] } ], "source": [ "print(type(\"Babička\"))\n", "print(type(46878678676848648486)) # long v Pythonu 2, int v Pythonu 3\n", "print(type(list())) # instance typu list\n", "print(type(list)) # samotný typ list" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "\n", "print(isinstance(list(), list)) # Funkce \"isinstance\" kontroluje typ objektu (včetně dědičnosti)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Vytvoření instance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pokud máme datový typ (třídu), jeho instanci vytvoříme podobně, jako kdybychom jej chtěli zavolat, tj. pomocí kulatých závorek. Ostatně, to už jsme dělali u vestavěných typů typu tuple, dict, list.\n", "\n", "Fakticky se přitom volá konstruktor (viz níže)." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "objekt = []\n", "objekt2 = \n" ] } ], "source": [ "objekt = list() # Vytvoření nové instance typu \"list\"\n", "objekt2 = list # Toto není vytvoření instance! Jen nové jméno pro tentýž typ\n", "\n", "# Podíváme se, co jsme dostali\n", "print(f\"objekt = {objekt}\")\n", "print(f\"objekt2 = {objekt2}\")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "objekt2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Metody - použití" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Metoda objektu je funkce, která je s nějakým objektem svázaná (bez něj nemá význam) a operuje s jeho daty. Může také měnit vnitřní stav objektu, tj. hodnoty atributů.\n", "\n", "Volání metod se v Pythonu provádí pomocí *tečkového* zápisu, tj. mezi objekt a volanou metodu se vloží tečka: **objekt.metoda(argumenty)**" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[45, 46, 47, 48, 49]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "objekt = [45, 46, 47, 48] # Seznam je objekt, \"list\" je typ\n", "objekt.append(49) # voláme metodu \"append\" na seznam\n", "\n", "objekt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Metoda `append` nemá význam sama o sobě, pouze ve spojení s konkrétním seznamem - přidává do seznamu nový prvek." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Třídy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Třídou je jakýkoliv uživatelský typ. Podobně jako vestavěné typy nabízí metody a data (atributy), ovšem můžeme je libovolně definovat.\n", "\n", "Nejjednodušší definice prázdné třídy (klíčové slůvko `pass` slouží pro prázdné třídy a metody - obchází nevýhody odsazování):" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Trida: # Třída se bude jmenovat Trida (velké písmeno je zvyk, ne nutnost)\n", " pass # Třída bude prázdná" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Definice metody\n", "\n", "Definice metody musí být uvnitř bloku třídy. (*Pozn. Metody lze do třídy přidat i později, ale není to preferovaný způsob.*)\n", "\n", "Běžné metody (**instance method**) se volají na konkrétním objektu. Kromě nich existují i tzv. **metody třídy** a **statické metody**, které zde nebudeme probírat.\n", "\n", "Zvláštnost (*Pozn. Ano, je to opravdu divné.*) definice metod (narozdíl od C++, Javy a dalších jazyků) je ta, že první argument metody je objekt, na kterém je metoda volána. Bez toho by metoda vůbec nevěděla, se kterým objektem pracuje! Dle konvence (která se snad nikdy neporušuje) se tento argument nazývá **self**. Při volání metody se pak vynechává a Python jej automaticky doplní." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Konstruktor\n", "\n", "Metoda, která inicializuje objekt - zavolá se na prázdném objektu ve chvíli, kdy vytvoříme novou instanci.\n", "Můžeme jej definovat, ale nemusíme - v takovém případě se použije výchozí konstruktor, který jednoduše nedělá nic (zvláštního). Konstruktor se v Pythonu vždy jmenuje **\\_\\_init\\_\\_** (dvě podtržítka před i po)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data\n", "\n", "Python příliš nerozlišuje mezi metodami a daty (tak jako obecně u proměnných -- vše je objekt). Pro něj je vše **atribut** daného objektu. Nastavení hodnoty se provádí podobně jako ukládání do proměnné, ale musíme přidat objekt a tečku. (*Pozn. Interně jsou atributy uložené ve slovnících a při přístupu k nim se prochází slovník samotného objektu, jeho třídy, jejích nadřazených tříd, ...*). Atribut daného jména nemusí přitom vůbec existovat, nemusí se nijak deklarovat." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "class Auto:\n", " def __init__(self, spotreba): # Konstruktor s argumentem\n", " self.spotreba = spotreba # Argument uložíme jako atribut objektu (objekt je zde self)\n", "\n", " def ujed(self, vzdalenost): # pozor - první argument metody je vždy \"self\"\n", " # Zde použiji atribut \"spotreba\"\n", " spotrebovane_litry = vzdalenost / 100 * self.spotreba\n", " # spotrebovane_litry je lokální proměnná, nikoliv atribut\n", " print(f\"Jedu {vzdalenost} kilometru a spotrebuju {spotrebovane_litry} litru benzinu.\")\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Jedu 100 kilometru a spotrebuju 7.5 litru benzinu.\n" ] } ], "source": [ "auto = Auto(7.5) # Vytvoření instance třídy Auto se spotřebou 7.5\n", "auto.ujed(100) # Při volání se *self* už nepíše" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "ujed() takes 2 positional arguments but 3 were given", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[11], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mauto\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mujed\u001b[49m\u001b[43m(\u001b[49m\u001b[43mauto\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m100\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# Chyba! Všimněte si, na jaký počet argumentů si Python stěžuje.\u001b[39;00m\n", "\u001b[0;31mTypeError\u001b[0m: ujed() takes 2 positional arguments but 3 were given" ] } ], "source": [ "auto.ujed(auto, 100) # Chyba! Všimněte si, na jaký počet argumentů si Python stěžuje." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Moje rachotina má spotřebu 15 l/100 km.\n", "Jedu 150 kilometru a spotrebuju 22.5 litru benzinu.\n" ] } ], "source": [ "rachotina = Auto(15)\n", "print(f\"Moje rachotina má spotřebu {rachotina.spotreba} l/100 km.\")\n", "rachotina.ujed(150)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Seznam všech atributů získáme pomocí `dir`" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "'spotreba, ujed'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# My zde filtrujeme na běžné atributy (ty s podtržítky jsou vždy něčím výjimečné)\n", "\", \".join(item for item in dir(rachotina) if not item.startswith(\"_\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cvičení\n", "\n", "Rozšiřte třídu `Auto` o správu najetých kilometrů. Měli bychom tedy mít možnost u auto:\n", "1. Podívat se na celkový počet najetých kilometrů.\n", "2. Zjistit celkovou spotřebu paliva.\n", "\n", "*Bonus:* Přidejte ještě podporu servisních intervalů. \n", "* Konstruktor bude mít parametr `servisni_interval` s výchozí hodnotou 20 000. \n", "* Přidejte metodu `je_potreba_servis`, která bude vracet `True` nebo `False` podle toho, zda je potřeba servis. \n", "* Přidejte metodu `proved_servis`, která zaznamená provedení servisu.\n", "* Rozšiřte metodu `ujed`, aby hlásila potřebu servisu 1000 km předem." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Vlastnosti (properties)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vlastnosti jsou \"chytřejší\" data. Umožnují vstoupit do procesu čtení nebo nastavování atributu. Hodí se to například tehdy, pokud objekt má několik navzájem závislých parametrů a my je nechceme ukládat nezávisle; pokud chceme kontrolovat, jaká hodnota se ukládá; či pokud chceme s ukládanou nebo čtenou hodnotou ještě něco zajímavého provést (viz příklad pro kruh).\n", "\n", "Ze syntaktického hlediska musíme nejdříve definovat metodu, která nese jméno vlastnosti a která tuto vlastnost \"čte\" (resp. vrací její hodnotu). O řádek výše musíme umístit tzv. *dekorátor* (tento koncept teď nebudeme podrobně vysvětlovat, jen jej pasivně použijeme) **@property**. Chceme-li, můžeme pak vytvořit i metodu pro zápis - ta se musí jmenovat stejně, požadovat jeden argument (ukládaná hodnota) a být uvedena dekorátorem **@*jmenovlastnosti*.setter**. Podobně bychom mohli vytvořit i metodu pro mazání (dekorátor **@*jmenovlastnosti*.deleter**), ale to se již běžně nedělá.\n", "\n", "Jakmile máme takto vytvořené vlastnosti, přistupujeme k nim jako k běžným datovým atributům - voláme je bez závorek a přiřazujeme do nich pomocí znaménka \"rovná se\".\n", "\n", "*Pozn. Vlastnosti fungují podobně jako properties v C# či javabeans v Javě. Povšimněte si však, že pro přístup k vlastnostem se používá úplně stejný zápis jako pro přístup k datovým atributům. Pokud tedy budeme chtít někdy změnit chování datového atributu a udělat z něj vlastnost, klient naší třídy to nepozná a nebude muset dělat žádné změny v kódu. Není tedy vhodné přespříliš iniciativně vytvářet triviální vlastnosti, které jen obalují přístup k atributům (jako by se to jistě dělalo v Javě).*" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import math\n", "import numbers\n", "\n", "class Kruh:\n", " def __init__(self, r):\n", " self.polomer = r\n", "\n", " @property # Chceme definovat vlastnost pro čtení\n", " def obsah(self): # Vypadá jako obyčejná metoda\n", " return math.pi * self.polomer ** 2\n", "\n", " @obsah.setter # Chceme nastavit zápis do dříve definované vlastnosti\n", " def obsah(self, s):\n", " print(\"Měním obsah na {}\".format(s))\n", " if not isinstance(s, numbers.Number): # Kontrolujeme, že jde o číslo\n", " raise TypeError(\"Obsah musi byt cislo\")\n", " self.polomer = math.sqrt(s / math.pi)\n", "\n", " @obsah.deleter\n", " def obsah(self):\n", " raise Exception(\"Mazat obsah kruhu nedava smysl.\")\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "r = 1\n", "S = 3.141592653589793\n" ] } ], "source": [ "kruh = Kruh(1)\n", "print(\"r = {}\".format(kruh.polomer)) # Normální datový atribut\n", "print(\"S = {}\".format(kruh.obsah)) # Property" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Měním obsah na 3\n", "r = 0.9772050238058398\n", "S = 3.0\n" ] } ], "source": [ "kruh.obsah = 3 # Změníme obsah pomocí zapisovatelné vlastnosti\n", "print(\"r = {}\".format(kruh.polomer)) # Změnil se nám i poloměr\n", "print(\"S = {}\".format(kruh.obsah)) # Property" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Měním obsah na Asi jako největší český rybník, který se jmenuje Rožmberk.\n" ] }, { "ename": "TypeError", "evalue": "Obsah musi byt cislo", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[19], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Zkusíme, co nám udělá kontrola v \"setteru\" vlastnosti obsah\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mkruh\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mobsah\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAsi jako největší český rybník, který se jmenuje Rožmberk.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", "Cell \u001b[0;32mIn[14], line 16\u001b[0m, in \u001b[0;36mKruh.obsah\u001b[0;34m(self, s)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mMěním obsah na \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(s))\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(s, numbers\u001b[38;5;241m.\u001b[39mNumber): \u001b[38;5;66;03m# Kontrolujeme, že jde o číslo\u001b[39;00m\n\u001b[0;32m---> 16\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mObsah musi byt cislo\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpolomer \u001b[38;5;241m=\u001b[39m math\u001b[38;5;241m.\u001b[39msqrt(s \u001b[38;5;241m/\u001b[39m math\u001b[38;5;241m.\u001b[39mpi)\n", "\u001b[0;31mTypeError\u001b[0m: Obsah musi byt cislo" ] } ], "source": [ "# Zkusíme, co nám udělá kontrola v \"setteru\" vlastnosti obsah\n", "kruh.obsah = \"Asi jako největší český rybník, který se jmenuje Rožmberk.\"" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "ename": "Exception", "evalue": "Mazat obsah kruhu nedava smysl.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[20], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# A další nesmyslná operace, které naše vlastnost zabrání\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m kruh\u001b[38;5;241m.\u001b[39mobsah\n", "Cell \u001b[0;32mIn[14], line 21\u001b[0m, in \u001b[0;36mKruh.obsah\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[38;5;129m@obsah\u001b[39m\u001b[38;5;241m.\u001b[39mdeleter\n\u001b[1;32m 20\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mobsah\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[0;32m---> 21\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mMazat obsah kruhu nedava smysl.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", "\u001b[0;31mException\u001b[0m: Mazat obsah kruhu nedava smysl." ] } ], "source": [ "# A další nesmyslná operace, které naše vlastnost zabrání\n", "del kruh.obsah" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Zapouzdření" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "V Pythonu není tento (pro OOP klíčový) koncept příliš dodržen. Zásady OOP tvrdí, že data by neměla být zvenčí přístupná, jiné jazyky obvykle nabízejí způsob, jímž lze schovat i některé metody (jako klíčová slova private, protected v C++, Javě). Python toto neřeší, ve výchozím nastavení je vše přístupno. Obvyklá konvence:\n", "\n", "* Na data objektů (pokud třída není vyloženě jednoduchá) se zvenčí nesahá.\n", "* Metody, jejichž jméno začíná podtržítkem, se zvenku nevolají (protože nejsou součástí \"veřejného\" rozhraní objektu).\n", "* Pokud chceme data chránit, můžeme pro ně vytvořit vlastnost (property).\n", "* Případné odlišnosti a obecně způsob, jakým se s metodami a daty nakládá, by měly být uvedeny v dokumentaci třídy.\n", "* Existují způsoby, jak zapouzdření lze vynutit (předefinování způsobu přístupu k atributům, ...), které se však příliš nepoužívají.\n", "\n", "Python ovšem na oplátku nabízí velmi vysokou úroveň **introspekce**, neboli schopnosti dozvědět se informace o objektech (jejich typ, atributy apod.) za běhu." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Podtržítkové konvence\n", "V Pythonu obecně jsou konvence velice silně zakořeněné. Na objektech to je vidět snad nejvíce.\n", "\n", "1. \"Soukromé\" atributy (atributem se v Pythonu často rozumí jak data tak metody -- vše je objekt) se pojmenovávají s podtržítkem na začátku, tj. např. `_soukroma_metoda`.\n", "2. Dvě podtžítka na začátku názvu atributu jej navíc přejmenují, takže je opravdu těžké se na něj odkazovat mimo kontext dané třídy.\n", "3. Atributy se dvěma podtžítky na začátku i na konci mají speciální význam (viz [dokumentace](https://docs.python.org/3/reference/datamodel.html#special-method-names)). Už jsme viděli `__init__`, podíváme se na několik dalších.\n", " * `__repr__` a `__str__` převádějí objekt na řetězec.\n", " * `__getattr__` a `__setattr__` slouží pro čtení a ukládání nenalezených atributů.\n", " * `__call__` se zavolá pokud použijeme objekt jako funkci.\n", " * `__doc__` obsahuje dokumentaci (docstring).\n", " * `__dict__` obsahuje slovník se jmenným prostorem obejktu.\n", " * ... dále existují speciální funkce pro logické operátory, pro emulaci funkcionality kontejnerů (iterace, položky, řezy), pro aritmetické operace atd." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['__class__',\n", " '__delattr__',\n", " '__dir__',\n", " '__doc__',\n", " '__eq__',\n", " '__format__',\n", " '__ge__',\n", " '__getattribute__',\n", " '__gt__',\n", " '__hash__',\n", " '__init__',\n", " '__init_subclass__',\n", " '__le__',\n", " '__lt__',\n", " '__ne__',\n", " '__new__',\n", " '__reduce__',\n", " '__reduce_ex__',\n", " '__repr__',\n", " '__setattr__',\n", " '__sizeof__',\n", " '__str__',\n", " '__subclasshook__']" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# co všechno obsahuje instace typu object\n", "dir(object())" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['__annotations__',\n", " '__call__',\n", " '__class__',\n", " '__closure__',\n", " '__code__',\n", " '__defaults__',\n", " '__delattr__',\n", " '__dict__',\n", " '__dir__',\n", " '__doc__',\n", " '__eq__',\n", " '__format__',\n", " '__ge__',\n", " '__get__',\n", " '__getattribute__',\n", " '__globals__',\n", " '__gt__',\n", " '__hash__',\n", " '__init__',\n", " '__init_subclass__',\n", " '__kwdefaults__',\n", " '__le__',\n", " '__lt__',\n", " '__module__',\n", " '__name__',\n", " '__ne__',\n", " '__new__',\n", " '__qualname__',\n", " '__reduce__',\n", " '__reduce_ex__',\n", " '__repr__',\n", " '__setattr__',\n", " '__sizeof__',\n", " '__str__',\n", " '__subclasshook__']" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# a co obsahuje jednoduchá funkce\n", "def foo(x):\n", " \"\"\"Toto je funkce foo\"\"\"\n", " return x\n", "dir(foo)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dědičnost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Třída může svoje chování (i data) odvozovat od nějaké jiné třídy, čímž si ušetříme spoustu práce při opakování společných rysů. V takovém případě řekneme, že naše nová třída (dceřinná) od té původní (rodičovské) dědí.\n", "\n", "* V dceřinné třídě můžeme změnit definici některé metody z rodičovské třídy.\n", "* Konstruktory se standardně dědí (*Na rozdíl od C++ či Javy, kde se musí explicitně volat, v Pythonu se volají jen pokud definujeme nový konstruktor a chceme zavolat i nadřazený.*)\n", "* Instance dceřinné třídy se mohou použít kdekoliv, kde počítá s objektem rodičovské třídy. *Toto platí v Pythonu ještě obecněji - obvykle se nekontrolují konkrétní typy, projde jakýkoliv objekt, který nabízí používané atributy/metody.*\n", "\n", "**Syntax:** Jméno rodičovské třídy se dává do závorky za jméno (místo object, od kterého třídy obvykle dědí)." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Clovek:\n", " def __init__(self, jmeno): # Konstruktor, který nastaví atribut \"jmeno\"\n", " self.jmeno = jmeno\n", "\n", " def rekni(self, veta): # Výchozí definice metody \"rekni\"\n", " print(type(self).__name__ + \": \" + veta)\n", "\n", " def predstav_se(self):\n", " self.rekni(\"Jmenuji se %s.\" % self.jmeno)\n", "\n", " def pozdrav(self):\n", " self.rekni(\"Dobrý den.\")\n", "\n", " def rozluc_se(self):\n", " self.rekni(\"Nashledanou.\")\n", "\n", "\n", "class Elektrikar(Clovek):\n", " def oprav_televizi(self): # Nová metoda v rodičovské třídě - jiný Clovek ji neumí\n", " self.rekni(\"Bude to v cuku letu.\")\n", " print(\"---Elektrikar něco šudlá.---\")\n", " self.rekni(\"A je to.\")\n", "\n", " def predstav_se(self): # Předefinovaná metoda \"predstav_se\" využívá atribut rodičovské třídy\n", " self.rekni(\"Já sem ňákej %s.\" % self.jmeno)\n", "\n", "class Nemocny(Clovek):\n", " def rekni(self, veta): # Předefinovaná metoda, která staví na rodičovské metodě.\n", " \"\"\"Nemocný má plný nos a co chvíli kýchne.\"\"\"\n", " trantab = \"\".maketrans(\"nmNM\", \"dbDB\")\n", "\n", " Clovek.rekni(self, veta.translate(trantab)) # Volání rodičovské metody\n", " self.kychni()\n", "\n", " def kychni(self): # Nová metoda v rodičovské třídě - jiný Clovek ji neumí\n", " print(\"---Heeeepččččíííík---\")" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Nemocny: Dobrý ded.\n", "---Heeeepččččíííík---\n", "Elektrikar: Dobrý den.\n", "Nemocny: Jbeduji se Tobáš Bardý.\n", "---Heeeepččččíííík---\n", "Elektrikar: Já sem ňákej Franta Vopička.\n", "Nemocny: Opravte bi, prosíb, televizi.\n", "---Heeeepččččíííík---\n", "Elektrikar: Bude to v cuku letu.\n", "---Elektrikar něco šudlá.---\n", "Elektrikar: A je to.\n", "Nemocny: Báte bé deskodalé díky.\n", "---Heeeepččččíííík---\n", "Elektrikar: Nashledanou.\n", "Nemocny: Dashledadou.\n", "---Heeeepččččíííík---\n" ] } ], "source": [ "\n", "elektikar = Elektrikar(\"Franta Vopička\")\n", "nemocny = Nemocny(\"Tomáš Marný\")\n", "\n", "# Rozhovor ze života\n", "nemocny.pozdrav() # Všimněte si, že \"pozdrav\" je rodičovská metoda, ale volá se \"rekni\" z dceřinné třídy.\n", "elektikar.pozdrav()\n", "nemocny.predstav_se()\n", "elektikar.predstav_se()\n", "nemocny.rekni(\"Opravte mi, prosím, televizi.\")\n", "elektikar.oprav_televizi()\n", "nemocny.rekni(\"Máte mé neskonalé díky.\")\n", "elektikar.rozluc_se()\n", "nemocny.rozluc_se()" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "ename": "AttributeError", "evalue": "'Nemocny' object has no attribute 'oprav_televizi'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[27], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mnemocny\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moprav_televizi\u001b[49m() \u001b[38;5;66;03m# Nemocný neumí opravit televizi\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# A elektrikář neumí kýchnout\u001b[39;00m\n", "\u001b[0;31mAttributeError\u001b[0m: 'Nemocny' object has no attribute 'oprav_televizi'" ] } ], "source": [ "nemocny.oprav_televizi() # Nemocný neumí opravit televizi\n", "# A elektrikář neumí kýchnout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pokud bychom chtěli nemocného elektrikáře, tak bychom museli sáhnout po vícenásobné dědičnosti\n", "a museli bychom ošetřit, že se správně volají rodičovské metody. Ještě lépe bychom využili tzv. mixiny a do objektů jejich stav přidávali dynamicky,\n", "ale to už je opravdu pokročilé téma, zabývat se jím nebudeme." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Dědění od vestavěných typů" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lze dědit i od vestavěných typů (a je to mnohdy i užitečné, ačkoliv to náš příklad nedokazuje)." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A že bys poprosil!?\n", "None\n" ] } ], "source": [ "# Ukázka seznamu, který nevrátí svoji položku, pokud jej nepoprosíme.\n", "class NerudnySeznam(list):\n", " def __getitem__(self, index): # Předefinujeme speciální metodu, která slouží k přístupu k položkám\n", " if isinstance(index, tuple) and index[1].lower()[:6] in (\"prosim\"):\n", " return list.__getitem__(self, index[0]) # Voláme původní metodu z typu \"list\"\n", " else:\n", " print(\"A že bys poprosil!?\")\n", " return None\n", "\n", "s = NerudnySeznam((1, 2, 3, 4))\n", "print(s[1])\n" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "print(s[1, \"prosim\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pokročilá témata" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Všechna následující témata jsou hrozně zajímavá a příšerně užitečná, nicméně nemáme na OOP tolik času, abychom se jim mohli věnovat. Přesto doporučujeme, abyste si o nich něco přečetli, budete-li mít chvilku času." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Vícenásobná dědičnost\n", "* Metody třídy\n", "* Statické metody\n", "* Abstraktní třídy\n", "* Polymorfismus\n", "* Metatřídy\n", "* Návrhové vzory\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 2 }