Třídy a objekty
Základní pojmy
Vše, co lze udělat pomocí tříd, lze nějak udělat pomocí funkcí a datových struktur. OOP (Objektově orientované programování) má však své výhody. OOP především pomáhá zdrojový kód zpřehlednit. Proč a jak programovat pomocí OOP zde rozebírat nebudu. To by bylo vydalo na celou knihu. Pokud nechcete, nepoužívejte. (Ale nepoužívat na větších projektech objekty v Pythonu je fakt úchylárna). Pokud chcete, nasaďte si černé brýle, začněte se tvářit velmi důležitě a doufejte, že vás při čtení této kapitoly nepřistihne OOP znalý programátor.
Třída, laicky řečeno,
slouží k definování objektu – z čeho se objekt skládá, jak je velký
atd. V Pythonu je třídou například datový typ int
,
seznam (list), n-tice … prostě všechny datové typy (čímž se liší od mnoha
jiných objektových jazyků).
Objekt je instancí třídy. Objekt už obsahuje reálná
data, jejichž struktura je popsaná třídou.
Například celočíselná proměnná je instancí třídy int
, tedy
objektem typu int
. Objekt
může obsahovat metody a atributy (definované třídou). (Každý objekt v Pythonu obsahuje atribut __doc__
a mnoho dalších).
Metody objektu jsou
funkce, které byly zapsané v těle třídy, jejíž instancí objekt je.
K metodám objektu se přistupuje přes tzv. tečkovou notaci. Setkali
jste se již s třídou seznam a jejími
metodami.
Pro znalce C++: všechny metody v Pythonu se chovají, jako by byly virtual
.
Atributy objektu slouží k uchovávání nějakých dat. Objekt, který nemá metody, ale jen atributy, je téměř totéž jako struktura :-}. Oproti struktuře má každý objekt minimálně dvě speciální metody: Jedna se nazývá konstruktor, druhá destruktor. Pokud je nevytvoříte Vy, udělá to za Vás Python. Ale na to ještě příjde řeč.
Konstruktor je speciální
metoda, která je automaticky volána pří vzniku objektu. Vy ji jinak
vyvolat nemůžete. I když konstruktor nedefinujete, je součástí třídy
(vytvoří jej za vás Python). Konstruktor je vždy pojmenovaný
__init__()
a může mít libovolný počet argumentů.
Destruktor je speciální
metoda, která je automaticky volána při zániku objektu (např. při
zrušení objektu příkazem del, nebo při ničení lokálních proměných
při opuštění jmenného prostoru těla funkce). Jindy ji volat nelze. I když
konstruktor nedefinujete, je součástí třídy (vytvoří jej za vás
Python). Destruktor je vždy pojmenovaný __del__()
.
Destruktor má jen jeden parametr (self
), který odkazuje na instanci
třídy, ale na to ještě příjde řeč.
Další speciální metody.
V Pythonu může mít třída další speciální metody (krom konstruktoru a destruktoru),
které se volají automaticky při nějaké události
(třeba při každém čtení nebo zápisu atributu objektu).
Speciální metody poznáte podle toho, že začínají a končí dvěma podtržítkama
(ale tím že napíšete metodu se dvěma podtržítkama na začátku a na konci ji
ještě speciální neuděláte. Python má jasně definovanou sadu speciálních metod –
jak se jmenují a při jaké příležitosti se vykonávají).
Dědičnost tříd Python také podporuje. Lze vytvořit třídu, která převezme (zdědí) metody a atributy jiné již existující třídy a přidá k nim vlastní. Taková třída se nazývá potomkem. Třída, po které dědí, se nazývá předchůdcem. (Též se někdy říká rodičovská a dceřiná třída). V Pythonu je možné dědit od více tříd (podobně jako v C++).
Interface Python nepodporuje, takže jestli nevíte co to je, nepátrejte po tom :-).
Třídy a objekty
Třída se definuje pomocí klíčového slova class
.
pass
Za dvojtečkou následuje blok definující atributy a metody třídy. Ve třídě může být také docstring.
Objekt (instance třídy) se vytvoří podobně, jako byste volali funkci.
Nečekejte žádné new
jako v jiných jazycích.
Uvnitř závorek mohou být požadovány argumenty, která se předávají konstruktoru (argumenty požadované konstruktorem).
Jelikož i datové typy jsou v Pythonu objekty, lze i objekt třídy, řekněme,
float
vytvořit tímto způsobem (zde je povinný jeden argument - hodnota čísla):
>>> print(x)
3.0
Asi vás (ne)překvapí, že třída je v Pythonu také objektem. Můžete jí přiřadit
do proměnné (přesněji řečeno přiřadit jí jiné jméno), nemůžete jí použít dokud se
nevykoná kód s definicí třídy (na rozdíl třeba od PHP, kde můžete nejdříve vytvářet instance
tříd a definici třídy mít až o pár řádek dále) a můžete jí zrušit příkazem del
.
Třída a atribut
Vytvoříme třídu pojmenovanou Trida1
, která bude obsahovat
atribut cislo
.
"""Tato trida obsahuje jen atribut jmenem cislo""" # docstring tridy
cislo = 0
Nyní můžete vytvořit instanci třídy trida1
(objekt typu Trida1
).
A nyní je možné přes tečkovou notaci přistupovat k atributu objektu.
'Tato trida obsahuje jen atribut jmenem cislo'
>>> instance.cislo
0
>>> instance.cislo = 5
>>> instance.cislo
5
Atributy nemohou být v Pythonu soukromé (nebo protected) jak možná znáte z jiných programovacích jazyků.
Do objektu můžete přiřadit atribut který není součástí definice třídy, jako by se nechumelilo. Přiřazená hodnota může být i funkce – tím vytvoříte novou metodu objektu!
Třída a metoda
Metoda je funkce zapsaná v těle třídy. Ale může to být i metoda definovaná
mimo tělo třídy a do třídy přiřazená (pomocí tečkové notace).
De-facto je metoda atributem typu function
.
Argument metody self
Speciálním argumentem metody je tzv. self
argument.
Jedná se o první argument, který se každé metodě
objektu při jejím volání automaticky předává.
Self
zastupuje ukazatel na instanci třídy (objekt), ze které je volán.
Název self není povinný, ale je pro první argument metody silně doporučovaný, patří to do „best practice“.
Pokud budete potřebovat přistupovat v metodách k atributům objektu, uděláte to přes
tečkovou notaci stejně, jako v předcházejícím příkladě. Jenom místo názvu
instance použijete atribut self
(pochopíte za chvilku).
Metoda
Metoda se v těle třídy definuje stejně jako funkce. Za její první argument se dosadí ukazatel na aktuální instanci (self). Je tedy zřejmé, že metoda musí mít nejméně jeden argument. Hodnoty, dosazované při volání funkce za argumenty, se předávají postupně od druhého argumentu.
... """Tato trida slouzi k pochopeni vytvareni metod"""
... cislo = 0 # atribut tridy
... cislo2 = 0 # ---- || ----
... def metoda1(self): # definice prvni metody
... "Tato metoda vynasobi cislo*cislo2"
... return self.cislo * self.cislo2
... def metoda2(self, a, b): # definice druhe metody
... "Dosadi 'a' za atribut cislo a 'b' za atribut cislo2"
... self.cislo = a
... self.cislo2 = b
...
>>> objekt = Trida2()
>>> objekt.metoda2.__doc__
"Dosadi 'a' za atribut cislo a 'b' za atribut cislo2"
>>> objekt.metoda2(5, 5)
>>> objekt.metoda1()
25
Příkaz objekt.metoda2(5, 5)
by se dal nahradit příkazy
objekt.cislo=5; objekt.cislo2=5
. Použití metody je
ovšem viditelně elegantnější. Nehledě na to, že metody obvykle provádějí
s argumenty složitější operace než jen přiřazení. Toto ale není kurz
na výuku programování, ale na výuku syntaxe jazyka Python …
Konstruktory a destruktory
Konstruktor je speciální metoda, která je volána při vzniku objektu, destruktor naopak při zániku.
def __init__(prvni_argument_je_self): # konstruktor
print("Cislo 3 zije!")
def __del__(self): # destruktor
print("Cislo tri umrelo")
>>> t = Trida3()
Cislo 3 zije!
>>> del t
Cislo tri umrelo
>>> def test():
local = Trida3() # vytvořím lokální proměnnou
# která po ukončení těla funkce sama zanikne
>>> test()
Cislo 3 zije!
Cislo 3 umrelo
>>>
Všimněte si, že se první argument se u metody nemusí jmenovat self.
Konstruktor lze samozřejmě využívat chytřeji. Například je tam možné vytvořit
nové atributy třídy a inicializovat je nějakou hodnotou (např. přiřazením self.cislo = 0
).
Třeba pomocí funkce vracející aktuální čas můžete přiřadit do atributu čas vzniku objektu,
protože se funkce __init__() zavolá při vytváření objektu.
Konstruktor může mít více argumentů, destruktor nikoliv.
Dědičnost
Python umí dědit z více než jedné třídy. Bohužel, takto kapitola je už moc dlouhá, takže ukážu jen příklad jednoduchého dědění a nebudu dědičnost probírat moc do hloubky.
Dědičnost se syntakticky zapisuje tak, že se za jméno třídy následníka do závorek zapíší jména předků oddělená čárkou (pokud jich je více). V příkladu dědí třída CelaKladnaCisla jen od jedné třídy (CelaCisla).
def __init__(self):
self.c = []
def _over(self, co):
if type(co) == type(0):
return True
else:
return False
def add(self, co):
if self._over(co):
self.c = self.c + [co]
class CelaKladnaCisla(CelaCisla):
"""Následník (potomek) třídy CelaCisla
Překrývá metodu _over, která je volána v předkovi
pro ověření, zda má být prvek přidán do seznamu nebo ne."""
def __init__(self):
CelaCisla.__init__(self) # volam konstruktor predka
def _over(self, co):
""" Zavolá metodu předka _over a pokud projde,
ověří ještě zda je číslo >= 0"""
return CelaCisla._over(self, co) and (co >= 0)
Podtržítko před metodou nic neznamená. Je ale dobrým zvykem jej používat
u metod, které mají být „protected“ U soukromých (private) metod se píší
na začátek dvě podtržítka. Ani to však nezaručí skutečnou soukromost metod. Python
alespoň interně změní jméno atributu (funkce)
z __soukromy
na _classname__soukromy
.
>>> ckc.add(5)
>>> ckc.add(-5)
>>> ckc.c
[5]
>>> ckc.add('text')
>>> ckc.c
[5]
Všiměte si, jak se v potomku volají metody předka – napíše se jméno třídy předka, tečka, jméno metody, předá se self argument a případně všechny další.
Každý objekt má speciální atribut __class__
, který odkazuje
na třídu, ze které je objekt odvozen.
>>> a.__class__
<class 'int'>
Funkce isinstance(obj, class)
vrací True,
pokud je obj instancí třídy class.
True
>>> isinstance(5.5, int)
False
>>> ckc = CelaKladnaCisla()
>>> isinstance(ckc, CelaCisla)
True
>>> isinstance(ckc, CelaKladnaCisla)
True
>>> cc = CelaCisla()
>>> isinstance(cc, CelaCisla)
True
>>> isinstance(cc, CelaKladnaCisla)
False
Funkce issubclass(class1, class2)
vrací True,
pokud je class1 potomkem třídy class2.
True
>>> issubclass(float, int) or issubclass(int, float)
False
>>> issubclass(CelaCisla, CelaKladnaCisla)
False
>>> issubclass(CelaKladnaCisla, CelaCisla)
True
Další speciální metody
Kromě dvou zmíněných speciálních metod konstruktoru a destruktoru podporuje Python ještě další kouzelné metody (magic methods).
Kompletní přhled všech magických metod najdete v dokumentaci a to buď pro Python 2.7 nebo Python 3 (každá verze Pythonu podporuje trošičku jinou sadu metod).
Metoda | Událost, při které je volána |
---|---|
__str__ | Metoda je volána funkcí str() a vrací řetězec, který pak vrací funkce str(). Užitečné všude tam, kde se převádí objekt na textovou reprezentaci. |
__getattr__(self, name) | Metoda, která je volána při čtení atributu objektu, který neexistuje. |
__setattr__(self, name, value) | Metoda, která je volána při přiřazení hodnotě atributu objektu (i těch co existují). |
__lt__(self, other) | Menší než (less than). |
__le__(self, other) | Menší nebo rovno (less or equal). |
__eq__(self, other) | Rovno (equal). |
__ne__(self, other) | Nerovno (not equal). |
__gt__(self, other) | Větší než (greather than). |
__ge__(self, other) | Větší nebo rovno (greather or equal). |
Metoda __setattr__ se dá s výhodou použít na vyhození výjimky při přiřazení k neexistujícímu atributu, k čemuž dochází většinou kvůli překlepu.
Výjimky se budou probírat až v další kapitole, takže vám teď nemusí být jasné co přesně dělají …
zasobnik = None
def __init__(self, a, b = 0):
print('Volá se init')
self.zasobnik = []
self.zasobnik.append(a)
self.zasobnik.append(b)
def __del__(self):
print('Vola se destruktor')
print(self.zasobnik)
def __setattr__(self, name, value):
if name not in dir(self):
raise AttributeError('Pokus o přiřazení do neexistujícího atributu ' + name + ' hodnoty ' + str(value))
self.__dict__[name] = value # pro Python 2.7 a starší
# object.__setattr__(self, name, value) # pro Python 3.0 a novější
def __gt__(self, other):
print('Vola se greather than')
if(isinstance(other,self.__class__)):
return self.zasobnik > other.zasobnik
else:
raise TypeError('Neumim porovnat')
def __eq__(self, other):
print('Vola se equal')
if(isinstance(other, self.__class__)):
return self.zasobnik == other.zasobnik
else:
raise TypeError('Neumim porovnat')
Projděte si tento příklad a zkuste uhádnout, proč se chovají objekty jak se chovají. Pod příkladem je vysvětlení.
- >>> o1 = Test(1)
- Volá se init
- >>> o = Test(1)
- Volá se init
- >>> o2 = Test(2)
- Volá se init
- >>> o == o1
- Vola se equal
- True
- >>> o1 != o2
- True
- >>> o1 > o
- Vola se greather than
- False
- >>> o1 >= o
- Traceback (most recent call last):
- File "<pyshell#8>", line 1, in <module>
- o1 >= o
- TypeError: unorderable types: test() >= test()
- >>> o1 < o2
- Vola se greather than
- True
- >>> o1 > 1
- Vola se greather than
- Traceback (most recent call last):
- File "<pyshell#10>", line 1, in <module>
- o1 > 1
- File "<pyshell#1>", line 20, in __gt__
- raise TypeError('Neumim porovnat')
- TypeError: Neumim porovnat
- >>> o1 < 1
- Traceback (most recent call last):
- File "<pyshell#11>", line 1, in <module>
- o1 < 1
- TypeError: unorderable types: test() < int()
- >>> o1.zasobnik.append(3)
- >>> o1.zasobink.append(3)
- Traceback (most recent call last):
- File "<pyshell#13>", line 1, in <module>
- o1.zasobink.append(3)
- AttributeError: 'test' object has no attribute 'zasobink'
- >>> o1.zasobink = []
- Traceback (most recent call last):
- File "<pyshell#14>", line 1, in <module>
- o1.zasobink = []
- File "<pyshell#1>", line 13, in __setattr__
- raise AttributeError('Pokus o přiřazení do neexistujícího atributu ' + name + ' hodnoty ' + str(value))
- AttributeError: Pokus o přiřazení do neexistujícího atributu zasobink hodnoty []
- >>> del o1
- Vola se destruktor
- [1, 0, 3]
- Řádek 1, 3, 5:
- Vytvářejí se nové instance, takže se volá konstruktor.
- Řádek 7:
o == o1
- Volá se o.__eq__(o1)
- Řádek 10:
o1 != o2
-
Třída
test
nemá definovanou metodu __ne__, takže Python k porovnání použije ID objektů. A protože o1 není aliasem o2 (není to stejný objekt), výsledek nerovnosti je True. - Řádek 12:
o1 > o
- Volá se o1.__gt__(o)
- Řádek 12:
o1 >= o
- Metoda __ge__ není definována, dojde k výjimce.
- Řádek 20:
o1 < o2
- o1 sice nemá definováno __lt__,ale o2 má definováno __gt__, tak se zavolá o2.__gt__(o1)
- Řádek 23:
o1 > 1
- Volá se o1.__gt__(1)
- Řádek 31:
o1 < 1
-
Python 3.0: o1 nemá __lt__, zavolá se metoda int._gt__(o1), která vyvolá výjimku.
Python 2.7: Vrátí True! Proč tomu tak je, viz Porovnávání různých typů. - Řádek 37:
o1.zasobink.append(3)
- Překlep ve jméně způsobí, že Python nenajde atribut
zasobink
v objektu o1, nemůže proto vrátit odkaz na metoduappend
a vyhodí výjimku. Nemá to nic společného s magickými metodami definovanými v třídětest
. - Řádek 42:
o1.zasobink = []
- Tentokrát už se volá o1.__setattr__('zasobink',[]). Protože atribut 'zasobink' v objektu
neexistuje, není nalezen v jeho jmenném prostoru a metoda __setattr__ vyhodí výjimku
AttributeError
.
O třídách toho lze napsat mnohem, mnohem více, ale tady už na to není místo. V případě zájmu doporučuji prostudovat dokumentaci.