prev up next title end end

12. Třídy a instance

  1. Objektově orientované programování
  2. Vytvoření třídy
    1. Minimum minimórum
    2. Konstruktor objektu třídy
    3. Objekt coby argument funkce
  3. Atributy
    1. Datové atributy
    2. Funkční atributy - metody
  4. Příklad: obdélník
  5. Instance
    1. Instance jsou měnitelné
    2. Instance jako výstupní hodnoty
    3. Instance uvnitř f-stringu
  6. Kopírování třídy
  7. Dekorátory
    1. Vytvoření dekorátoru
    2. Klauzura s parametry
    3. Dekorátor s parametrem
    4. Zkrácená verze dekorátoru s parametrem
    5. Dekorátor 'wraps'
    6. Dekorace funkce třídou
    7. Dekorace třídy funkcí
  8. Speciální metody a atributy
  9. Datové třídy
  10. Dědění
  11. Propojení tříd
  12. Glosář
  13. Cvičení

12.1 Objektově orientované programování

Python je programovací jazyk, který podporuje objektově orientované programování (OOP). Vše v Pythonu je objekt.

Generátorem objektů jsou třídy, z nichž jsou všechny objekty odvozeny coby instance. Třídy samotné jsou rovněž odvozené děděním ze základní třídy object.
S řadou vestavěných tříd (str, int, float, ...), označovaných jako vestavěné typy, jsme se setkali již v počátečních kapitolách.
Termíny třída (class) a typ (type) jsou v Python3 v podstatě synonyma. V Python2 to byly dvě různé entity. Je obvyklé označovat vestavěné (built-in) třídy slovem typ.


12.2 Vytvoření třídy

12.2.1   Minimum minimórum

Nejjednodušší definice třídy vypadá takto:

class Emanuel:
    '''Nepovinný dokumentační řetězec - docstring '''
    pass

Záhlaví složené z klíčového slova class, názvu třídy a dvojtečky je vše, co jednoduchá třída potřebuje.
Později se dovíme, že třída může dědit ze své nadtřídy, v tom případě by před dvojtečkou byl název supertřídy v závorkách, například class Emanuel(Motyle). Pokud není supertřída uvedena, je supertřídou základní třída object.

Následuje odsazené tělo třídy, které bude obsahovat řadu dalších objektů. V naší demonstraci jsme uvedli příklad dokumentačního řetězce a jedno klíčové slovo pass, které nedělá nic. Pro platnost minimální definice postačí jedno z uvedeného. Definice třídy končí přechodem na (neodsazený) počátek nového řádku.

Dokumentační řetězec (docstring) slouží jako stručná informace o funkci, třídě i modulu, vyvolaná příkazem (například pro třídu Emanuel):

Emanuel.__doc__      # chodí ve skriptu, nikoliv v konzole

S naší prostinkou třídou můžeme už provádět řadu regulérních úkonů, například:

Definice třídy se mohou vyskytnout kdekoli v programu, ale obvykle se umisťují poblíž počátku (po příkazech import).

12.2.2   Konstruktor objektu třídy Point

Konstrukci třídy si dále vysvětlíme na konkretním případě třídy Point, kterou si vytvoříme pro manipulaci s bodem ve dvourozměrném prostoru.

Abychom při každé deklaraci objektu nemuseli vytvářet jeho datové atributy ručně, použijeme takzvanou inicializační metodu __init__, kterou přidáme na začátek třídy a pomocí níž potřebné atributy objektu zavedeme předem. Této metodě (odst. 12.3.2) by se správně mělo říkat inicializátor ale je také často označována jako konstruktor.

 class Point:
     def __init__(self, x=0, y=0):        # konstruktor
         self.x = x
         self.y = y  

Prvním parametrem definice __init__ je povinné slovo self, které zastupuje dosud neexistující (ale předpokládanou) instanci třídy. Další parametry s počáteční hodnotou zajišťují, že každá nově vytvořená instance bude implicitně obsahovat datové atributy x, y, zde s počáteční nulovou hodnotou.

Polohu bodu v rovině určíme jeho souřadnicemi. V matematickém zápisu bodu se tyto souřadnice, oddělené čárkou, uvádějí v závorce. Například, (0, 0) představuje počátek souřadnic, (x, y) představuje bod ve vzdálenosti x ve vodorovném a y ve svislém směru od počátku.

Vytvoření nové instance připomíná volání funkce:

>>> p = Point()             # vytvoření objektu (instance třídy)

Užitečnost konstruktoru si ukážeme ve spojení s další metodou, která určí vzdálenost bodu od počátku souřadnic:

 class Point:
     def __init__(self, x=0, y=0 ):           # konstruktor 
         self.x = x
         self.y = y
     def distance_from_origin(self):          # metoda instance
         return(self.x**2 + self.y**2)**0.5		 

Při vytváření instance musíme zadat hodnotu atributů, deklarovaných v metodě __init__. Pokud jsou tyto atributy deklarovány s počátečními hodnotami, které nám vyhovují - potom nemusíme:

>>> p = Point()
>>> p.x, p.y
(0, 0)
Můžeme zajisté vytvořit objekt s vlastními hodnotami x,y:
>>> g = Point(3,4)
>>> g.x, g.y
(3, 4)
>>> g.distance_from_origin()
5.0

12.2.3   Objekt coby argument funkce

Instanci můžeme zadat jako parametr obvyklým způsobem:

def print_point(obj):
    print('%s, %s' % (obj.x, obj.y))
>>> print_point(g)
3, 4
>>> print_point(p)
0, 0

12.3 Atributy

Slovem atribut označujeme obecně vlastnosti nějakého objektu, v tomto případě jím označujeme vlastnosti třídy a instance. Atributy třídy i instance jsou dvojího druhu - datové a funkční.

Datovými atributy jsou proměnné, funkčními atributy jsou metody, což jsou funkce, definované uvnitř třídy a volané pro instanci (objekt) tečkovou notací. Atributem je také případný dokumentační řetězec.

12.3.1   Datové atributy

Pro instanci i třídu lze určit atributy i dodatečně (jak jsme již viděli v předběžné ukázce):

>>> p.x = 3              # datový atribut 'x' instance 'p'  
>>> p.y = 4              # datový atribut 'y' instance 'p' 

>>> Point.a = "alef"     # datový atribut 'a' třídy Point

Tato skladba je podobná skladbě pro výběr atributu z modulu, např. math.pi nebo string.ascclr-ii_uppercase. Jak moduly, tak i třídy a instance vytvářejí své vlastní jmenné prostory. Přístup k jejich jménům (atributům) je v obou případech stejný - přes tečkovou notaci. V naší ukázce vytváříme atributy tím, že jim přiřazujeme hodnoty.

Ověřme si to:

>>> p.x, p.y
(3,4)
>>> Point.a
'alef'

Tečkovou notaci můžeme použít jako součást jakéhokoliv výrazu, takže následující příkazy jsou legální:

>>> print('%d  %d' % (p.x, p.y))
3 4
>>> print(p.x*p.x + p.y*p.y)
25

Stejně snadné jako vytvoření objektu a deklarace jeho atributů (případně atributů třídy), je jejich smazání příkazem del:

>>> del p.y
>>> del Point.a
>>> del p

12.3.2   Funkční atributy - metody

Metoda je funkce, deklarovaná uvnitř třídy. Pro třídu v Pythonu se rozlišují tři metody: instanční, statická a metoda třídy:

  1. Instanční metoda definuje chování objektu (instance), nikoli třídy. Definice obsahuje parametr self. V odstavci 12.2.2 uvedená speciální metoda __init__ je eminentní metoda instanční.
    class Sample:                     deklarace třídy
        val = 10                      datový atribut třídy
        def myFunc(self):             metoda instance
            print("Value: ", self.val)
    
    >>> obj = Sample()                vytvoření instance
    >>> obj.myFunc()                  volání metody
    Value:  10
    
  2. Metoda třídy definuje chování celé třídy, nikoli objektu. Definice obsahuje parametr cls a je uvedena vestavěným dekorátorem @classmethod.
    Tato metoda má přístup k proměnné třídy a ke statické metodě, nemá přístup k datům instance.
    class Hample:
        ham = 20                       datový atribut třídy
    
        @classmethod                   dekorace
        def myFunc(cls):               metoda třídy
            print("Hodnota proměnné třídy: ", cls.ham)
            cls.otherFunc()            volání staticé metody
    
        @staticmethod                  dekorace
        def otherFunc():               statická metoda
            print("Výstup statické metody.")
    
    >>> Hample.myFunc()                volání metody třídy
    Hodnota proměnné třídy:  20        výstup metody třídy
    Výstup statické metody.            výstup statické metody
    
  3. Statická metoda je obyčejná funkce, deklarovaná uvnitř třídy. Deklarace je uvedena vestavěným dekorátorem @staticmethod (viz ad 2).
    Tato metoda má přístup pouze ke svým vlastním atributům. Obě metody lze volat i přes vytvořenou instanci:
  4. >>> hamp = Hample()
    >>> hamp.myFunc()
    Hodnota proměnné třídy:  20
    Výstup statické metody.
    >>> hamp.otherFunc()
    Výstup statické metody.
    

12.4 Příklad: obdélník

Řekněme, že chceme, aby třída představovala obdélník (rectangle). Otázkou je, jakou informaci musíme poskytnout, abychom určili obdélník? Pro zjednodušení předpokládejme, že je obdélník orientován buďto vodorovně nebo svisle, nikdy není pootočen.

Máme několik možností: můžeme určit střed obdélníka (dvě souřadnice) a jeho velikost (šířku a výšku), nebo můžeme určit jeden z jeho rohů a velikost, nebo můžeme určit polohu dvou protilehlých bodů. Zvolíme si levý horní roh obdélníka a jeho velikost.

class Rectangle:
    def __init__(self, posice, w, h):
        self.corner = posice
	self.width = w
	self.height = h
Parametr posice předpokládá zadání dvojice čísel - entici. Pro její vložení můžeme použít nepojmenovaný objekt již deklarované třídy Point:
>>> bod = Rectangle(Point(), 100, 200)
>>> bod.width, bod.height
(100, 200)
>>> bod.corner.x, bod.corner.y
(0, 0) 

Objektu Point() jsme nezadali žádné argumenty, protože nám zřejmě vyhovují jeho implicitní hodnoty. Kdybychom chtěli mít počátek obdélníka jinde než v bodě (0,0), mohli bychom například zadat:

>>> box = Rectangle(Point(10,50), 100, 200)
>>> box.corner.x, box.corner.y
(10,50)
>>> box.width, box.height
(100,200)

Vzájemné vztahy objektů vidíme na následujícím obrázku: atribut 'corner' objektu 'box' odkazuje na objekt třídy Point s atributy 'x' a 'y'.


12.5 Instance

12.5.1   Instance jsou měnitelné

Změnu instance můžeme provést změnou některého z jejich atributů a to přiřazením. Abychom například změnili velikost obdélníka bez změny jeho polohy, změníme hodnoty width a height:

box.width = box.width + 50
box.height = box.height + 100

Změnu atributů můžeme zobecnit do funkce:

def grow_rectangle(obj, dwidth, dheight):
    obj.width += dwidth
    obj.height += dheight

Můžeme také napsat funkci pro změnu polohy obdélníka:

def move_rectangle(obj, dx, dy):
    obj.corner.x += dx
    obj.corner.y += dy


12.5.2   Instance jako výstupní hodnoty

Instance může být výstupní hodnotou funkce. Například, funkce find_center přijímá instanci třídy Rectangle jako argument a vrací instanci třídy Point, která obsahuje souřadnice středu zadaného obdélníka:

def find_center(obj):
    p = Point() 
    p.x = obj.corner.x + obj.width/2.0
    p.y = obj.corner.y + obj.height/2.0
    return p

Když si definice tříd Point (odst. 12.2.2), Rectangle (odst 12.4) a funkcí find_center (12.5.2), print_point (12.2.3) vložíme do jednoho skriptu, můžeme v konzole Pythonu psát:

>>> box = Rectangle(Point(40,60), 100, 200)
>>> center = find_center(box)
>>> print_point(center)
90.0, 160.0

Je také možné deklarovat instanci s použitím entice,

>>> boot = Rectangle((40,60), 100, 200)

instance obsahuje atribut corner ale hodnoty tohoto atributu nemají jména:

>>> boot.corner
(40,60)
>>> boot.corner.x
AttributeError: 'tuple' object has no attribute 'x'

Pro takto deklarovaný objekt nám samozřejmě nebudou chodit funkce distance_from_origin() (odst. 12.2.2), print_point(obj) (odst. 12.2.3) a find_center(obj), protože všechny používají jména x, y. Bude nám chodit funkce grow_rectangle(obj,dwidth,dheight) (odst. 12.5.1).


12.5.3   Instance uvnitř f-stringu

Instanci lze reflektovat i uvnitř f-stringu (viz kap. 6.12). Máme-li například definovanou třídu v souboru katalog.py:

class Komik:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    def __str__(self):
        return f"{self.first_name} {self.last_name}, věk {self.age}."

    def __repr__(self):
        return f"{self.first_name} {self.last_name}, věk {self.age}. Překvapení!" 

můžeme ji importovat do interaktivní konzoly, vytvořit instanci a tu posléze aktivovat v f-stringu:

>>> import katalog
>>> komik = katalog.Komik("Jan", "Plíšek", "74")
>>> f"{komik}"                  případně  f"{komik!s}"
'Jan Plíšek, věk 74.'
>>> f"{komik!r}"                viz 6.14 Formátování řetězců clr-ii
'Jan Plíšek, věk 74.  Překvapení!'      viz soubor katalog.py

Poznámku 'Překvapení!' jsme při uplatnění konverze !r ve výměnném poli získali proto, že jsme si ji nadefinovali v metodě __repr__(self).


12.6 Kopírování třídy

Při kopírování třídy či objektu třídy (instance) použijeme vždy metodu copy nebo deepcopy z modulu copy.
Pro objekty tříd, které neobsahují vnořené objekty, postačí tak zvaná mělká kopie (shallow copy), která vytvoří nezávislou kopii nevnořených objektů. Příkladem takového objektu je třída Point z odst. 12.2.2:

>>> import copy
 
>>> p1 = Point(3,4)
>>> p2 = copy.copy(p1) 
>>> p1 is p2
False                              # mají různá ID
>>> p1.x == p2.x and p1.y == p2.y
True                               # mají stejné souřadnice

Příkladem vnořeného objektu je atribut corner třídy Rectangle , jejíž dva nevnořené objekty jsou atributy x, y - viz odst. 12.4.

Vytvoříme-li obdélník b1 a jeho kopii b2 funkcí copy,

>>> b1 = Rectangle(Point(), 100, 200)
>>> b2 = copy.copy(b1)
vytvoří se nezávislé kopie pouze atributů with a height, zatímco atribut corner je společný pro obě prezentace b1 a b2:

Nezávislou hlubokou kopii (deep copy) i vnořených objektů vytvoříme metodou deepcopy:

>>> b2 = copy.deepcopy(b1)

12.7 Dekorátory

12.7.1   Vytvoření dekorátoru

Dekorátor je funkce vyššího řádu, která jako argument přijímá dekorovanou funkci a vrací svou vnitřní funkci (klauzuru), s oblibou nazývanou wrapper().

Účelem dekorátoru je změna chování dekorované funkce bez její modifikace. Úlohou klauzury je popsat tuto modifikaci.

Mechanizmus dekorátorů vychází z toho, že funkce může být zadána jako argument, definována uvnitř jiné funkce, vrácena jinou funkcí a být přiřazena k proměnné.

Dekorátorová funkce:
def decorator(func):   # dekorátor pro dekorovanou funkci 
    def wrapper():                     
        print("Před spuštěním ovečky")
        func()         # interní volání dekorované funkce
        print("Po spuštění ovečky")
    return wrapper
	
Dekorovaná funkce (nevadí, že se nejmenuje func()):
@decorator                                 # jedna varianta                     
def decorated_function():              
    print("Ovečka se pěkně pase.")

#ovečka = decorator(decorated_function)    # druhá varianta

# invokace s dekorátorem:  decorated_function()
# invokace s přiřazením:  ovečka()

Nezbytné propojení dekorátoru a dekorované funkce se provádí dvojím způsobem:

  1. dekorací názvu dekorované funkce - což je v podstatě přiřazení, provedené interně interpretem Pythonu:
    # decorated_function = decorator(decorated_function): 
    @decorator    
    def decorated_function():      
        print("Ovečka se pěkně pase.")
    
    >>> decorated_function()    
    Před spuštěním ovečky
    Ovečka se pěkně pase.
    Po spuštění ovečky
    
  2. deklarací vztahu pod textem obou funkcí. Důležitou částí uvedeného přiřazení je formulace pravé strany. Levou stranu lze pojmenovat jakkoli:
    decorated_function = decorator(decorated_function)
    nebo třeba:
    ovečka = decorator(decorated_function)
    

    Invokací decorated_function() nebo ovečka() se volá dekorátorová funkce, jejímž argumentem je dekorovaná funkce:

    >>> ovečka()
    Před spuštěním ovečky
    Ovečka se pěkně pase.
    Po spuštění ovečky
    

Pro pochopení popisovaného mechanizmu je vhodné připomenout, že při volání dekorované funkce se nejprve provádí tělo dekorátorové funkce.

Existujícím dekorátorem lze dekorovat jakoukoli vhodnou funkci i metodu třídy.

@decorator
def beran():      
    print("Berany, berany duc!")
>>> beran()
Před spuštěním ovečky
Berany, berany duc!
Po spuštění ovečky

Rovněž je možné jednu funkci dekorovat více dekorátory:

def decorator(ovce):              # dekorátorová funkce           
    def wrapper():                       
        print("Před spuštěním ovečky")
        ovce()                           
        print("Po spuštění ovečky")
    return wrapper
		
def lines(ovce):                  # dekorátorová funkce
    def wrapper():
        print("- " * 12)
        ovce()
        print("- " * 12)
    return wrapper  		
		
@decorator
@lines
def beran():                      # dekorovaná funkce     
    print("Berany, berany, duc!")

Zpracování kódu začíná prvním dekorátorem nad názvem dekorované funkce:

>>> beran()
Před spuštěním ovečky
- - - - - - - - - - - - 
Berany, berany, duc!
- - - - - - - - - - - - 
Po spuštění ovečky

12.7.2   Klauzura s parametry

Jako ukázku si uvedeme dekorátor, který zabrání dělení nulou. Povšimněte si shodných parametrů u klauzury, odkazu na ovečku i u vlastní ovečky (dekorované funkce).

def smart_divide(func):
   def wrapper(a,b):            # klauzura s parametry 
      if b == 0:
         print("Nulou nelze dělit!")
         return                 # uplatní se jen při b == 0
      return func(a,b)          # nezbytný odkaz na ovečku
   return wrapper

@smart_divide
def divide(a,b):                # dekorovaná funkce
    return a/b
>>> divide(5,7)
0.7142857142857143
>>> divide(5,0)
Nulou nelze dělit!

Při práci s argumenty je vhodné používat sběrné parametry *args, *kwargs. Parametry vnitřní funkce (klauzury) wrapper musí být kompatibilní s parametry invokované funkce:

def logging(func):
   def wrapper(*args, **kwargs):               
      print('Příchozí argumenty', args, kwargs)
      output = func(*args, **kwargs)
      print("Výstup", output)
   return wrapper

@logging
def sumace(a, b, c, vasil=0):
  return a+b+c+vasil
>>> sumace(1, 2, 3, vasil=5)             # viz Poznámka
Příchozí argumenty (1, 2, 3) {'vasil': 5}
Výstup 11
>>> sumace(1, 2, 3)
Příchozí argumenty (1, 2, 3) {}
Výstup 6

Poznámka:
Při volání funkce je nutné zachovat počet pozičních argumentů a počet a název klíčového argumentu. Klíčový argument může chybět, poziční argumenty nikoliv. Vyzkoušejte si to.

12.7.3   Dekorátor s parametrem

Vlastní dekorátorová funkce (dekorátor) může mít pouze jeden parametr, jímž je nepřímý odkaz na dekorovanou funkci. Případný další parametr se připojuje k nové vnější funkci (kvazi dekorátor). Propojení přiřazením se v tomto případě vytváří mezi dekorovanou funkcí a kvazidekorátorem (z vnější dekorátorové funkce se stala vnitřní funkce).

Příklad plně vybaveného (kvazi) dekorátoru, jehož účelem je poskytnout argument dekorované funkci

def anonce(n):                       # kvazi dekorátor
    def decorator(func)              # dekorátor s klauzurou
        def wrapper(*args, **kwargs):        # klauzura
            result = func(*args, **kwargs)   # volání ovečky
            return result + n**n             # úprava ovečky
        return wrapper
    return decorator
 
@anonce(2)                 # argument je součástí dekorace
def add(x, y):             # dekorovaná funkce - říkejme ji ovečka
    return x + y
>>> add(5,7)
16                         # není to chyba: 5+7 + 2**2 = 16

12.7.4   Zkrácená verze dekorátoru s parametrem

Dekorátorovou funkci tvoří pouze kvazidekorátor a dekorátor:

def call(*argv, **kwargs):             # kvazidekorátor 
    def dekor(func):           # dekorátor coby klauzura  
        return func(*argv, **kwargs)       
    return dekor 
  
@call(5,7)                     # dekorace s argumenty
def add(x, y):                 # dekorovaná fce 
    return x + y 

12.7.5   Dekorátor 'wraps'

Systémový dekorátor @wraps umožňuje přístup k introspektivním atributům funkce. Tento dekorátor importujeme z modulu functools. Atribut .num_calls vrací počet volání funkce:

from functools import wraps

def counter(func): 
    @wraps(func)         # dekorace importovaným dekorátorem                     
    def wrapper(*args, **kwargs):          
        wrapper.num_calls += 1                # počítadlo
        print(f"Call {wrapper.num_calls} of   
  	             {func.__name__!r}:")     # viz Pozn.
        return func(*args, **kwargs)
    wrapper.num_calls = 0
    return wrapper

@counter                 # dekorace vlastní funkcí
def add(x, y):
    "Aplikován dekorátor 'wraps'"
    print(x + y)

Poznámka: Písmeno f uvádí takzv. f-string - viz kap. 6.12. Písmeno !r označuje druh konverze - viz kap. 6.11, sektor Konverze argumentu.

>>> add(5,7)
Call 1 of 'add':
12
>>> add(5,7)
Call 2 of 'add':
12

>>> add.__name__    ==> 'add'
>>> add.__doc__     ==> "Aplikován dekorátor 'wraps'"
>>> add.__module__  ==> '__main__'  

Pro výše uvedené vlastnosti je doporučeníhodné používat importovaný dekorátor @wraps v hojné míře.

12.7.6   Dekorace funkce třídou

Při dekorování funkce třídou zastupuje klauzuru wrapper speciální metoda __call__ :

class Pohoda:
    def __init__(self, fun):             # konstruktor
        self.fun = fun 
    def __call__(self, *args, **kwargs): 
        # Sem může přijít vhodný kód  
        self.fun(*args, **kwargs)         # volání 'ovečky' 
        # Sem rovněž může přijít vhodný kód 
              
@Pohoda                                   # dekorace třídou
def pozdrav(name, message ='Nazdar'):     # ovečka 
    print("{} {}".format(message, name)) 
>>> pozdrav("Pavle!")
Nazdar Pavle!

Uvedená ukázka je poněkud schematická, protože výstup "Nazdar Pavle!" bychom zajistili i bez dekorátoru. Napravíme to další ukázkou, ve které nám dekorátor doplní výstup z dekorované třídy:

class Decorator:
   def __init__(self, x):
        self.x = x
   def __call__(self, a):
        print('Druhá mocnina', a, 'je', self.x(a))
                    # výraz self.x(a) je invokace ovečky
@Decorator
def square(a):
   return a*a
>>> square(5)
Druhá mocnina 5 je 25

12.7.7   Dekorace třídy funkcí

import functools
import time

def timer(func):
    "Vytiskne délku výpočtu dekorované funkce či třídy"
    @functools.wraps(func)                 
    def wrapper(*args, **kwargs):
        start = time.perf_counter()  
        val = func(*args, **kwargs)
        run_time = time.perf_counter() - start  
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return val
    return wrapper

@timer
class Calculator:
    def __init__(self, num):
        self.num = num
        import time
        time.sleep(2)

    def double_and_add(self):
        "Vrátí sumaci stejně modifikovaných členů řady"
        res = sum([i * 2 for i in range(self.num)])
        print("Result : {}".format(res))
>>> c = Calculator(100)
Finished 'Calculator' in 2.0111 secs
>>> c.double_and_add()
Result : 9900

Dekorátor @timer můžeme použít i k dekoraci funkce.


12.8 Speciální metody a atributy

Pro zvídavé

Speciální (také magické) jsou všechny předdefinované metody a atributy tříd, jejichž název je ohraničen dvěma podtržítky, jako například výše popsaná metoda __init__. Jejich alternativní označení v angličtině je dunders (double underscores).

Výpis předdefinovaných metod a atributů pro určitý objekt získáme příkazem dir(), například pro slovník (list):

>>> dir([1,2,3])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
 '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__len__',
 '__lt__',  '__mul__', '__ne__', '__new__', '__reduce__',  '__reduce_ex__',
 '__repr__', '__reversed__',  '__rmul__', '__setattr__', '__setitem__',
 '__sizeof__', '__str__', '__subclasshook__',
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert',
 'pop', 'remove', 'reverse', 'sort']

Speciální metody jsou interně evokované při explicitním volání příslušných operátorů a vestavěných funkcí. Například porovnání "ariel" == "alias" je realizováno speciální metodou __eq__:

>>> "ariel".__eq__("alias")
False

Speciální metodě můžeme zadat specifické chování. Například sečíst souřadnice dvou bodů (provést vektorový součet):

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __add__(self, other):    # speciální metoda (instanční)
        return self.x + other.x, self.y + other.y		
>>> p1 = Point(1, 2)
>>> p2 = Point(3, 4)
>>> p1 + p2                       # interně  p1.__add__(p2)
(4, 6)

Specielními atributy jsou datové objekty __doc__, __name__, __dict__, ... . Atribut __doc__obsahuje informaci o příslušném objektu; například o prázdném seznamu se dozvíme:

>>> [].__doc__
Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list.
The argument must be an iterable if specified. 

Dobrý přehled specielních metod lze nalézt na stránce tutoriálu Ponořme se do Pythonu 3


12.9 Datové třídy

Datová třída (dataclass) je speciální třída, vhodná pro ukládání datových objektů, která je vytvořená s pomocí dekorátoru @dataclass, importovaného z modulu dataclasses.

Tento dekorátor automaticky generuje všechny potřebné dunder metody (__init__(), __repr__(), ...), podporující zjednodušené zadávání datových atributů, zde zvaných pole (fields). Deklarace pole má skladbu:   name:   type   [ = value ].

dunder: společné označení metod, jejichž názvy jsou vymezeny dvojitými podtržítky (double-underscore).

from dataclasses import dataclass

@dataclass
class Contact:
    name: str
    email: str
    phone: str = "00 00 00 00" 
>>> contact = Contact("test","test@test.com", '72 63 08 56')
>>> contact
Contact(name='test', email='test@test.com', phone='72 63 08 56')

Lze použít také klíčové argumenty:

>>> contact1 = Contact(name="test1",email="test1@test.com")
>>> contact1
Contact(name='test1', email='test1@test.com', phone='00 00 00 00')

12.10 Dědění

Mechanizmus, zvaný dědění (inheritance) se používá při odvození nové třídy z třídy stávající. Odvozená třída (sub class, sub typ, potomek) přebírá atributy a metody (bázové) třídy výchozí (super class, rodič či předek).

Skladba pro deklaraci subtřídy je jednoduchá:

class NázevOdvozenéTřídy(NázevVýchozíTřídy):
    '''Nepovinný dokumentační řetězec - docstring '''
    pass

Kromě naznačeného dědění po jediném předkovi (simple heritance) existují další možnosti:

Instruktivní ukázka s použitím speciální metody __init__:

class Savci:
    vid = "Pozdrav Pánbůh"
    def __init__(self, druh):
        hid = "pane Randák"
        print(druh, 'je teplokrevný savec.')
		        
class Pes(Savci):
    def __init__(self):
        super().__init__('Pes')       Varianta: Savci.__init__(self, 'Pes')
	print('Pes je přítel člověka.')

Speciální metoda __init__ třídy Pes potlačí tutéž metodu třídy Savci. Toto chování však zruší následně definovaná metoda __init__ s objektem super() (nebo Savci), čímž umožňuje použití atributů děděné třídy.

>>> pajda = Pes()
Pes je teplokrevný savec.
Pes je přítel člověka.

Zadání: Vypusťte v definici třídy Pes řádek "super().__init__('Pes')" a a zkuste pro objekt pajda volat proměnné vid a hid.

Obsahuje-li zdrojová i děděná třída stejný atribut, platí pro děděnou třídu vlastní definice tohoto atributu:

class A:
    pes = "Dingo" 
    def zobraz(self):
        print('Jsem třída A.')

class B(A):
    pes = "Voříšek" 
    def zobraz(self):
        print('Jsem třída B.')
>>> obj = B()
>>> obj.pes
'Voříšek'
>>> obj.zobraz()
Jsem třída B.

12.11 Propojení tříd

Propojení tříd lze kromě dědění provést také takzvanou kompozicí. Propojení se uskuteční prostřednictvím instance třídy uvnitř jiné třídy:

class Raketa:
    def __init__(self, name, destinace):
        self.name = name
        self.distance = destinace
    def launch(self):
        return "%s dosedl na %s" % (self.name, self.destinace)
                             
class Lunochod():
    def __init__(self, name, destinace, maker):
        self.raketa = Raketa(name, destinace)   # deklarace instance
        self.maker = maker
    def report(self):
        return "%s byl spuštěn korporací %s" % (self.raketa.name, self.maker)
>>> z = Lunochod("Lunochod", "Měsíc", "EKA")
>>> z.raketa.launch()
'Lunochod dosedl na Měsíc'
>>> z.report()
'Lunochod byl spuštěn korporací EKA'

12.12 Glosář

třída (class)
Uživatelsky definovaný typ. Třídu lze považovat za prototyp objektů, určující jejich vlastnosti a chování.
instance
Konkrétní objekt určité třídy.
objekt (object)
Všechno v Pythonu je objekt. Zejména 'instance' se často označuje jako objekt.
konstruktor (constructor)
Metoda __init__, kterou Python automaticky použije při tvorbě nové instance.
atribut (attribute)
Společné označení pro proměnné třídy i objektů a metody.
mělká rovnost (shallow equality)
Rovnost odkazů, nebo dvou odkazů, které ukazují na stejný objekt.
hluboká rovnost (deep equality)
Rovnost hodnot, nebo dvou odkazů, které ukazují na objekty se stejnými hodnotami.
mělká kopie (shallow copy)
Kopie objektu včetně odkazů na vnořené objekty provedená příkazem copy.copy(item); vnořené objekty se nezkopírují, pouze odkazy.
hluboká kopie (deep copy)
Kopie objektu včetně jeho vnořených objektů provedená příkazem copy.deepcopy(item)
dunders (double underscores)
Alternativní označení dvojitých podtržítek (__), používaných při zápisu 'magických' metod a atributů tříd (viz 12.9).

12.13 Cvičení

  1. Do třídy Rectangle vložte metodu area, která vrací plochu obdélníka
    r = Rectangle(Point(0,0), 50,100)
    # test: r.area() --> 5000
    
  2. Do třídy Rectangle vložte metodu perimeter, která vrací obvod obdélníka
    r = Rectangle(Point(0,0), 50,100)
    # test: r.perimeter() --> 300
    
  3. Do třídy Rectangle vložte metodu flip, která zamění jeho šířku za výšku a obráceně
  4. Ke třídě Point (viz 12.2.2) přidejte metodu sklon, která vrátí úhel sklonu spojnice bodu x,y s počátkem 0,0 v radiánech. Její evokace může vypadat takto:

    >>> Point(4,10).sklon()
    Sklon v radiánech:  1.1902899496825317
    

    Ošetřte případ, kdy je spojnice kolmá k ose x (ψ = π/2).

  5. Upravte skript třídy Punktum:

    class Punktum:
        def __init__(self, x, y): 
            self.x = x
            self.y = y
    	def __repr__(self):
            return f"Souřadnice bodu: x = {self.x}," \
    		                        f"y = {self.y}"	
    

    tak aby její volání mělo následující výstup

    >>> Punktum(5,9)
    Souřadnice bodu: x = 5, y = 9
    

pre up next title end end