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.

Na začátku tohoto kurzuj sem psal, že je určen pro programátory znalých OOP. Následující text berte jako malé bezvýznamné opakování. Pokud OOP nerozumíte, nebojte, po přečtení této kapitoly mu nebudete rozumět dál.

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 :-).

To je konec malého OOOP (opakování objektověho orientovaného programování). Teď se podíváme na implementaci OOP v Pythonu.

Třídy a objekty

Třída se definuje pomocí klíčového slova class.

class NazevTridy:
    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.

nazev_objektu = NazevTridy ()

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):

>>> x = float(3)
>>> print(x)
3.0
Právě toho se využívá při přetypování.

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.

class Trida1:
    """Tato trida obsahuje jen atribut jmenem cislo"""   # docstring tridy
    cislo = 0
 

Nyní můžete vytvořit instanci třídy trida1 (objekt typu Trida1).

instance = Trida1()

A nyní je možné přes tečkovou notaci přistupovat k atributu objektu.

>>> instance.__doc__
'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!

instance.novyAtribut = 'hello world'

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.

>>> class Trida2:
...     """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.

>>> class Trida3:
     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).

class CelaCisla:            # použijeme jako předchůdce (rodiče)
        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 = CelaKladnaCisla()
>>> 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=5
>>> a.__class__
<class 'int'>

Funkce isinstance(obj, class) vrací True, pokud je obj instancí třídy class.

>>> isinstance(5, int)
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.

>>> issubclass(bool, int)
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).

MetodaUdá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í …

class Test:
        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í.

  1. >>> o1 = Test(1)
  2. Volá se init
  3. >>> o = Test(1)
  4. Volá se init
  5. >>> o2 = Test(2)
  6. Volá se init
  7. >>> o == o1
  8. Vola se equal
  9. True
  10. >>> o1 != o2
  11. True
  12. >>> o1 > o
  13. Vola se greather than
  14. False
  15. >>> o1 >= o
  16. Traceback (most recent call last):
  17.   File "<pyshell#8>", line 1, in <module>
  18.     o1 >= o
  19. TypeError: unorderable types: test() >= test()
  20. >>> o1 < o2
  21. Vola se greather than
  22. True
  23. >>> o1 > 1
  24. Vola se greather than
  25. Traceback (most recent call last):
  26.   File "<pyshell#10>", line 1, in <module>
  27.     o1 > 1
  28.   File "<pyshell#1>", line 20, in __gt__
  29.     raise TypeError('Neumim porovnat')
  30. TypeError: Neumim porovnat
  31. >>> o1 < 1
  32. Traceback (most recent call last):
  33.   File "<pyshell#11>", line 1, in <module>
  34.     o1 &lt; 1
  35. TypeError: unorderable types: test() < int()
  36. >>> o1.zasobnik.append(3)
  37. >>> o1.zasobink.append(3)
  38. Traceback (most recent call last):
  39.   File "<pyshell#13>", line 1, in <module>
  40.     o1.zasobink.append(3)
  41. AttributeError: 'test' object has no attribute 'zasobink'
  42. >>> o1.zasobink = []
  43. Traceback (most recent call last):
  44.   File "<pyshell#14>", line 1, in <module>
  45.     o1.zasobink = []
  46.   File "<pyshell#1>", line 13, in __setattr__
  47.     raise AttributeError('Pokus o přiřazení do neexistujícího atributu ' + name + ' hodnoty ' + str(value))
  48. AttributeError: Pokus o přiřazení do neexistujícího atributu zasobink hodnoty []
  49. >>> del o1
  50. Vola se destruktor
  51. [1, 0, 3]
  52.  
Řá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 metodu append 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.

Komentář Hlášení chyby
Created: 11.9.2005
Last updated: 30.8.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..