6
Třídy

### Proč používat třídy?

### Základy

>>> class PrvniTrida:                      # definice třídy
...     def nastav(self, x):               # metoda třídy
...         self.data = x                  # self - instance 
...     def zobraz(self):
...         print self.data                # dané instance

>>> x = PrvniTrida()               # dvě instance
>>> y = PrvniTrida()               # dva jm. prostory

>>> x.nastav("Král Artuš")         # self – x, resp. y 
>>> y.nastav(3.14)                 # “PrvniTrida.nastav()”

>>> x.zobraz()                     # self.data je ...
Král Artuš
>>> y.zobraz()                     #  v obou instancích jiná
3.14

>>> x.data = "Novinka"             # můžeme přist. i přímo
>>> x.zobraz()
Novinka

>>> class DruhaTrida(PrvniTrida):  # dědí nastav
...     def zobraz(self):          # přetěžuje zobraz
...         print 'data = "%s"' % hodnota

>>> z = DruhaTrida()
>>> z.nastav(42)                   # z PrvniTrida
>>> z.zobraz()                     # z DruhaTrida
hodnota = "42"

>>> x.zobraz()                     # stále PrvniTrida
Novinka

>>> class TretiTrida(DruhaTrida):            # je DruhaTrida
...     def __init__(self, x):               # při TretiTrida(x)
...         self.data = x
...     def __add__(self, x):                # při self + x
...         return ThirdClass(self.data + x)
...     def __mul__(self, x):         
...         self.data = self.data * x        # při self * x

>>> a = TretiTrida("abc")          # volání __init__
>>> a.zobraz()                     # z DruhaTrida
data = "abc"

>>> b = a + 'xyz'                  # __add__ vytváří ...
>>> b.zobraz()                     #  novou instanci
data = "abcxyz"

>>> a * 3                          # zatímco __mul__ ...
>>> a.zobraz()                     #  mění původní
data = "abcabcabc"

### Příkaz class a jeho použití

class jmenotridy(materskatrida1, ...):
    jmenoatributu1 = hodnota1
    def jmenometody(self, parametr1, ...):
        self.jmenoatributu2 = hodnota2

class Materska: pass
class Potomek(Materska):           # dědíme z Materska
    data = 'něco'                  # přiřadíme atr. třídy
    def __init__(self, hodn):      #  a dalšímu
        self.data = hodn           # přiřadíme atr. instance
    def zobraz(self):
        print self.data, Potomek.data

>>> x = Potomek(1)                 # dva obj. typu instance
>>> y = Potomek(2)                 # každý vlastní “data”
>>> x.zobraz()                     # "self.data" se liší, ...
>>> y.zobraz()                     #  "Potomek.data" ne
1 něco
2 něco

### Metody a jejich použití

instance.metoda(argument1, ...)

trida.metoda(referencenainstanci, argument1, ...)

class Dalsi:                       # definice třídy a ...
    def tiskarnicka(self, text):   #  definice její metody
        print text

>>> x = Dalsi()                    # nová instance třídy a ...
>>> x.tiskarnicka('Jak se máme?')  #  volání její metody
Jak se máme?

>>> Dalsi.tiskarnicka(x, 'Jak se máme?')     # metoda třídy
Jak se máme?

### Dědičnost prochází jmennými prostory

>>> class Predek:
...     def metoda(self):
...         print 'Predek.metoda'
...

>>> class Potomek(Predek):
...     def metoda(self):                        # přetěžujeme
...         print 'začátek běhu Potomek.metoda'  #  něco přidáme
...         Predek.metoda(self)                  #  voláme pův.
...         print 'konec běhu Potomek.metoda' 
...

>>> x = Predek()
>>> x.metoda()                     # volání Predek.metoda
Predek.metoda

>>> x = Potomek()                  # Potomek.metoda volá ...
>>> x.metoda()                     #  Predek.metoda
začátek běhu Potomek.metoda
Predek.metoda
konec běhu Potomek.metoda

Predek.__init__(self, ...)

% cat special.py

class Predek:
    def metoda(self):
        print 'Predek.metoda' 
    def zastupce(self):
        self.akce()                # očekávaná metoda

class Dedic(Predek):
    pass

class Nahrazujici(Predek):
    def metoda(self):
        print 'Nahrazujici.metoda'

class Rozsirujici(Predek):
    def metoda(self):
        print 'začátek běhu Rozsirujici.metoda'
        Predek.metoda(self)
        print 'konec běhu Rozsirujici.metoda'

class Poskytovatel(Predek):
    def akce(self):
        print 'Poskytovatel.akce'

if __name__ == '__main__':
    for trida in (Dedic, Nahrazujici, Rozsirujici):
        print '\n' + trida.__name__ + '...'
        trida().metoda()
    print '\nPoskytovatel...'
    Poskytovatel().zastupce()

% python special.py

Dedic...
Predek.metoda

Nahrazujici...
Nahrazujici.metoda

Rozsirujici...
začátek běhu Rozsirujici.metoda
Predek.metoda
konec běhu Rozsirujici.metoda

Poskytovatel...
Poskytovatel.akce

### Přetěžování operátorů

% cat cislo.py

class Cislo:
    def __init__(self, hodnota):   # při Cislo(1)
        self.hodn = hodnota
    def __sub__(self, x):          # výsledkem nová instance
        return Cislo(self.hodn - x)

>>> from cislo import Cislo        # jméno z modulu
>>> X = Cislo(5)                   # Cislo.__init__(X, 5)
>>> Y = X - 2                      # Cislo.__sub__(X, 2)
>>> Y.data
3

>>> class DruhaMocnina:
...     def __getitem__(self, i):
...         return i ** 2
...

>>> X = DruhaMocnina()
>>> for i in range(5):
...     print X[i],                # volá __getitem__(X, i)
...
0 1 4 9 16

>>> class Prochazeni:
...     def __getitem__(self, i):
...         return self.data[i]    # vyhazuje IndexError auto.
...
>>> X = Prochazeni()               # instance Prochazeni
>>> X.data = "cosi"
>>>
>>> for item in X:                 # for sm. volá __getitem__
...     print item,                #  až do IndexError
...
c o s i
>>> 's' in X                       # opět __getitem__ 
1

>>> class BezAtributu:
...     def __getattr__(self, atrib_jmeno):
...         if atrib_jmeno == "vek": return 37
...         else: raise AttributeError, atrib_jmeno
...

>>> XY = BezAtributu()
>>> XY.vek
37
>>> XY.jmeno
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 4, in __getattr__
AttributeError: jmeno

>>> class HloupeScitani:
...     def __init__(self, x=0):
...         self.hodn = x          # inicializace
...     def __add__(self, y):
...         self.hodn += y         # bez nové instance (!)
...     def __repr__(self):
...         return `self.hodn`     # jen standardní konverze
...
>>> X = HloupeScitani(1)           # volá __init__
>>> X + 2; X + 2                   # volá __add__
>>> print X                        # volá __repr__
5

### Jmenné prostory a jejich pravidla

>>> class super:
...     def hello(self):
...         self.data1 = "spam"
...
>>> class sub(super):
...     def howdy(self):
...         self.data2 = "eggs"
...

>>> X = sub()                      # vytváří nový jmenný prostor
>>> X.__dict__                     #  což je vlastně slovník
{}
>>> X.hello()                      # mění jm. prostor instance
>>> X.__dict__
{'data1': 'spam'}

>>> X.howdy()                      # mění jm. prostor instance
>>> X.__dict__
{'data2': 'eggs', 'data1': 'spam'}

>>> super.__dict__
{'hello': <function hello at 88d9b0>, '__doc__': None}

>>> sub.__dict__
{'__doc__': None, 'howdy': <function howdy at 88ea20>}

>>> X.data3 = "toast"
>>> X.__dict__
{'data3': 'toast', 'data2': 'eggs', 'data1': 'spam'}

### Návrh a třídy

% cat pizzatm.py

class Zamestnanec:
    def __init__(self, jmeno, plat=0):
        self.jmeno = jmeno
        self.plat = plat
    def pridejNaPlatu(self, cast):
        self.plat += self.plat * cast
    def pracuj(self):
        print self.jmeno, "něco dělá"
    def __repr__(self):
        return "<Zamestnanec: jmeno=%s, plat=%s>" \
              % (self.jmeno, self.plat)

class SefKuchar(Zamestnanec):
    def __init__(self, jmeno):
        Zamestnanec.__init__(self, jmeno, 30000)
    def pracuj(self):
        print self.jmeno, "dělá jídlo"

class Cisnik(Zamestnanec):
    def __init__(self, jmeno):
        Zamestnanec.__init__(self, jmeno, 20000)
    def pracuj(self):
        print self.jmeno, "obsluhuje zákazníka"

class PizzaBot(SefKuchar):
    def __init__(self, jmeno):
        SefKuchar.__init__(self, jmeno)
    def pracuj(self):
        print self.jmeno, "dělá pizzu"

if __name__ == "__main__":
    bobik = PizzaBot('Bobík')      # nový PizzaBot Bobík
    print bobik                    # volá __repr__
    bobik.pridejNaPlatu(0.20)      # zvýšíme plat o 20%
    print bobik

    print "\nVyzkoušíme, jak naši zaměstnanci pracují:"
    for trida in Zamestnanec, SefKuchar, Cisnik, PizzaBot:
        obj = trida("Instance třídy " + trida.__name__)
        obj.pracuj()

C:\Python> python pizzatm.py

<Zamestnanec: jmeno=Bobík, plat=30000>
<Zamestnanec: jmeno=Bobík, plat=36000.0>

Vyzkoušíme, jak naši zaměstnanci pracují:
Instance třídy Zamestnanec něco dělá
Instance třídy SefKuchar dělá jídlo
Instance třídy Cisnik obsluhuje zákazníka
Instance třídy PizzaBot dělá pizzu

% cat pizzerie.tm

from pizzatm import PizzaBot, Cisnik

class Zakaznik:
    def __init__(self, jmeno):
        self.jmeno = jmeno
    def objednej(self, cisnik):
        print "%s\n přijímá objednávku od zák. '%s'" \
             % (cisnik, self.jmeno)
    def zaplat(self, cisnik):
        print "Platbu od zák. '%s' převzal\n %s" \
             % (self.jmeno, cisnik)

class Pec:
    def funguj(self):
        print "Pec peče"

class Pizzerie:

    def __init__(self):
        self.cisnik = Cisnik('Honza')        # vkládáme objekty,
        self.sefkuchar = PizzaBot('Bobík') 
        self.pec = Pec()

    def objednej(self, jm_zakaznika):
        zakaznik = Zakaznik(jm_zakaznika)    #  ... vytváříme
        zakaznik.objednej(self.cisnik)       #  ... a používáme
        self.sefkuchar.pracuj()
        self.pec.funguj()
        zakaznik.zaplat(self.cisnik)

if __name__ == "__main__":
    pokusna = Pizzerie()
    pokusna.objednej('Homer')      # Homerova objednávka
    print '...'
    pokusna.objednej('Pepa')       # Pepova objednávka

C:\Python> python pizzerie.py

<Zamestnanec: jmeno=Honza, plat=20000>
 přijímá objednávku od zák. 'Homer'
Bobík dělá pizzu
Pec peče
Platbu od zák. 'Homer' převzal
 <Zamestnanec: jmeno=Honza, plat=20000>
...
<Zamestnanec: jmeno=Honza, plat=20000>
 přijímá objednávku od zák. 'Pepa'
Bobík dělá pizzu
Pec peče
Platbu od zák. 'Pepa' převzal
 <Zamestnanec: jmeno=Honza, plat=20000>

% cat obal.py

class Obal:
    def __init__(self, objekt):
        self.obalovane = objekt 
    def __getattr__(self, jm_atributu):
        print 'Přístup k:', jm_atributu 
        return getattr(self.obalovane, jm_atributu)

>>> from obal import Obal
>>> x = Obal([1,2,3])              # obalíme seznam
>>> x.append(4)                    # deleguje metodě seznamu
Trace: append
>>> x.obalovane                    # print my member
[1, 2, 3, 4]

>>> x = Obal({"a": 1, "b": 2})     # obalíme slovník
>>> x.keys()                       # deleguje metodě slovníku
Trace: keys
['a', 'b']

% cat mnozina.py

class Mnozina:
    def __init__(self, hodnota = []):      # konstruktor
        self.data = []
        self.skladani(hodnota)

    def prunik(self, druha):       # druha – posloupnost
        res = []
        for x in self.data:
            if x in druha:         # společné do výsledku
                res.append(x)
        return Mnozina(res)        # nová Mnozina

    def sjednoceni(self, druha):   # druha – posloupnost
        res = self.data[:]         # ke kopii naší posloupnosti
        for x in druha:            #  přidáváme prvky z druhé
            if not x in res:
                res.append(x)
        return Mnozina(res)

    def skladani(self, hodn):      # hodn - seznam, Mnozina, ...
        for x in hodn:             # odstranuje duplikaty
            if not x in self.data:
                self.data.append(x)

    def __len__(self):             # při len(self)
        return len(self.data)
    def __getitem__(self, key):    # při self[i]
        return self.data[key]
    def __and__(self, druha):      # při self & druha
        return self.prunik(druha)
    def __or__(self, druha):       # při self | druha
        return self.sjednoceni(druha)
    def __repr__(self):            # mj. při print 
        return '<Mnozina: ' + `self.data` + '>'          

>>> class Trida:                   # bez __repr__
...     def __init__(self):
...         self.neco = "zradlo"
...
>>> X = Trida()
>>> print X                        # standardně jméno a adresa
<Spam instance at 87f1b0>

% cat vypisovac.py

# Vypisovac je mixin, ze kterého můžeme dědit __repr__ a trochu
# zkultivovat standardní výpis instance třídy jen se jménem 
# třídy a adresou. Je dobré si uvědomit, že self je v hierarchii
# vždy “ta nejníže”.

class Vypisovac:

    def __repr__(self):
        return ("<Instance třídy %s (na adrese %s):\n%s>" % \
         (self.__class__.__name__, id(self), self.atributy()))
      # jméno třídy, adresa objektu a atributy (jmeno = hodnota)

    def atributy(self):
        vysl = ''
        for a in self.__dict__.keys():  # procházení jm. prost.
            if a[:2] == '__': vysl += "\t%s – standardní\n" % a
            else: vysl += "\t%s = %s\n" % (a, self.__dict__[a])
        return vysl

% cat vypisovactest.py

from vypisovac import Vypisovac     # get tool class

class Predek:
    def __init__(self):            # konstruktor předka
        self.data1 = "něco"

class Potomek(Predek, Vypisovac):  # __repr__ z mixinu Vypisovac
    def __init__(self): 
        Predek.__init__(self)
        self.data2 = "kdesicosi"   # nějaké další atr. instance
        self.data3 = 42

if __name__ == "__main__":
    X = Potomek()
    print X                        # použití “vmíchané” __repr__

C:\python> python vypisovactest.py

<Instance třídy Potomek (na adrese 18781212:)
	data1 = něco
	data3 = 42
	data2 = kdesicosi
>

>>> from vypisovac import Vypisovac
>>> class x(Vypisovac): pass
... 
>>> t = x()
>>> t.a = 1; t.b = 2; t.c = 3
>>> t
<Instance třídy x (na adrese 18852172):
	a = 1
	c = 3
	b = 2
>

def tovarna(trida, *arg):     # ntice nezachycených (dle pozice)
    return apply(trida, arg)  # volá trida (args konsturktoru)

class Nejaka:
    def udelejTo(self, neco): print neco

class Osoba:
    def __init__(self, jmeno, povolani):
        self.jmeno = jmeno
        self.povolani = povolani

objekty = tovarna(Nejaka), tovarna(Osoba, "Guido", "guru")

def tovarna(trida, *dlepozice, **dlejmena):
    return apply(trida, dlepozice, dlejmena)

class Jakasi:
    def udelejTo(self, neco):
        print neco

objekt = Jakasi()
x = objekt.udelejTo                # obj. typu vázaná met.
x('tralala kočička se vdávala')    # instanci doplní Python

y = Jakasi.doit                    # odvázaná metoda
y(objekt, 'la la?')                # instanci ručně

### Věcičky

% cat dokument.py

"Jsem dokument.__doc__ a chci si hrat"

class Kterasi:
    "Jsem Kterasi.__doc__, resp. dokument.Kterasi.__doc__"
    def metoda(self, argument):
        "Jsem Kterasi.metoda.__doc__, resp. self.metoda.__doc__"
        pass

def funkce(x):
    "Jsem dokument.funkce.__doc__, x je makovy argument"
    pass

>>> import dokument
>>> dokument.__doc__
'Jsem dokument.__doc__ a chci si hrat'
>>> dokument.Kterasi.__doc__
'Jsem Kterasi.__doc__, resp. dokument.Kterasi.__doc__'
>>> dokument.Kterasi.metoda.__doc__
'Jsem Kterasi.metoda.__doc__, resp. self.metoda.__doc__'
>>> dokument.funkce.__doc__
'Jsem dokument.funkce.__doc__, x je makovy argument'

### Oblíbené problémy

>>> class X: a = 1
...
>>> I = X()
>>> I.a, X.a
(1, 1)

>>> X.a = 2       # pozor na vedlejší účinky!
>>> I.a           # změna třídy mění i instanci (I.a)
2

>>> J = X()       # opět – aktuální hodnoty, ne ty ve zdroj.k.
>>> J.a           # přiřazení J.a ale už neovlivní ani I, ani X
2

class X: pass                      # jen jako nekonstantní 
class Y: pass                      #  jmenné prostory

X.a = 1; X.b = 2; X.c = 3          # atributy tříd jako proměnné
Y.a = X.a + X.b + X.c

for X.i in range(Y.a): print X.i,  # vypíše 0 1 2 3 4 5

>>> class Zaznam: pass
...
>>> X = Zaznam()
>>> X.jmeno = 'Bobík'
>>> X.povolani  = 'Tvůrce Pizzy'

class Vypisovac:
    def __repr__(self): ...
    def dalsi(self): ...           # chceme dalsi z Vypisovac

class Predek:
    def __repr__(self): ...        #  a __repr__ z Predek
    def dalsi(self): ...

class Potomek(Predek, Vypisovac):  # __repr__ díky pořadí
    dalsi = Vypisovac.dalsi        # dalsi vybereme explicitně
    def __init__(self):
        ...

% cat pocitadlo.py

class Pocitadlo:
    pocet = 0
    def __init__(self):
        Pocitadlo.pocet += 1
    def vypisPocet():
        print "Počet instancí třídy Pocitadlo:", Pocitadlo.pocet

>>> from Pocitadlo import *
>>> a, b, c = Pocitadlo(), Pocitadlo(), Pocitadlo()
>>> Pocitadlo.vypisPocet()
Traceback (most recent call last):
  File "<interactive input>", line 1, in ?
TypeError: unbound method vypisPocet() must be called with 

 Pocitadlo instance as first argument (got nothing instead)

def vypisPocet2(): print "Počet:", Pocitadlo.pocet

>>> vypisPocet2()
Počet: 3

>>> d = Pocitadlo()
>>> a.vypisPocet()
Počet instancí třídy Pocitadlo: 4
>>> d.vypisPocet()
Počet instancí třídy Pocitadlo: 4
>>> Pocitadlo().vypisPocet()
Počet instancí třídy Pocitadlo: 5

def vytvorObjekt():
    class Trida:
        neco = 1
        def metoda(self): print Trida.neco
    return Trida()

vytvorObjekt().metoda()

class Trida:
    neco = 1
    def metoda(self): print Trida.neco

def vytvorObjekt(): return Trida()

### Shrnutí

### Cvičení

<Instance třídy Potomek(Predek, Vypisovac), na adrese 7841200:

class Simulator:
    def __init__(self)
     # vytváří instance tříd Zakaznik a Zamestnanec
    def spustSimulaci(self, jmenoJidla)
     # spustí simulaci objednávky daného jídla
    def vypisVysledek(self)
     # vypíše, co si zákazník objednal

class Zakaznik:
    def __init__(self)
     # na začátku má objednáno None
    def objednejJidlo(self, jmenoJidla, zamestnanec)
     # objedná u zamestnance jídlo “jmenoJidla”
    def vypisJidlo(self)
     # vypisuje jméno jídla, které si zákazník objednal

class Zamestnanec:
    def prijmiObjednavku(self, jmenoJidla)
     # vrací Jidlo daného jména

class Jidlo:
 # jen uchovává jméno předané konstruktoru
    def __init__(self, jmeno)