![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Python je programovací jazyk, který podporuje objektově orientované programování (OOP).
Při objektově orientovaném programování je pozornost zaměřena na vytváření objektů, které obsahují jak data, tak funkce či metody.
Téměř vše v Pythonu je objekt. Objekt je kolekce dat (proměnných) a metod (funkcí), které s danými daty pracují. Prototypem objektů jsou třídy
, z nichž jsou všechny objekty (čísla, řetězce, funkce, moduly, metody, atp) odvozeny coby instance
.
Třída je logické seskupení dat a funkcí. Ve své podstatě definuje nový, složený datový typ. Různé vestavěné typy - třídy Pythonu (str, int, float, ...
) používáme již od počátečních kapitol.
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 - class Emanuel(superClass):
.
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.
S naší prostinkou třídou můžeme už provádět řadu regulérních úkonů, například:
>>> Emanuel.strom = "ořech"
>>> eman = Emanuel()
>>> eman.míra = 158
Definice třídy se mohou vyskytnout kdekoli v programu, ale obvykle se umisťují poblíž počátku (po příkazech import).
Třídy samotné jsou instancemi prvotní třídy type (termín type je synonymem termínu class):
>>> x = [2, 5, 8], y = "Nazdar"# x, y jsou instancemi tříd list a str: >>> type(x), type(y) (<class 'list'>, <class 'str'>)# list a str jsou instancemi třídy type: >>> type(type(x)), type(type(y)) (<class 'type'>, <class 'type'>)# tudíž nepřekvapí že: >>> type(Point) <class 'type'>
Nebudeme ale předbihat. Konstrukci třídy si krok po kroku vysvětlíme na konkretním případě třídy Point, kterou si vytvoříme pro manipulaci s bodem ve dvourozměrném prostoru.
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.
class
Point:pass
Vytvořením třídy vytvoříme prototyp možných konkrétních objektů, neboli instancí třídy. Vytvoření nové instance připomíná volání funkce:
>>> p = Point()# vytvoření objektu (instance třídy)
Ke třídě Point se vrátíme v odstavci 12.4.
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.
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.ascii_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
Pro třídu v Pythonu se rozlišují tři metody: instanční, statická a metoda třídy:
class Sample:deklarace třídy val = 10datový atribut třídy def myFunc(self):metoda instance print("Value: ", self.val)
>>> obj = Sample()vytvoření instance >>> obj.myFunc()volání metody Value: 10
class Hample: ham = 20datový atribut třídy @classmethoddekorace def myFunc(cls):metoda třídy print("Hodnota proměnné třídy: ", cls.ham) cls.otherFunc()volání staticé metody @staticmethoddekorace def otherFunc():statická metoda print("Výstup statické metody.")
>>> Hample.myFunc()volání metody třídy Hodnota proměnné třídy: 20výstup metody třídy Výstup statické metody.výstup statické metody
>>> hamp = Hample() >>> hamp.myFunc() Hodnota proměnné třídy: 20 Výstup statické metody. >>> hamp.otherFunc() Výstup statické metody.
Protože naše třída Point má představovat matematické body v dvojrozměrném prostoru, všechny její instance by automaticky měly mít atributy x a y. Tak tomu však u našich objektů třídy Point
zatím není:
>>> p2 = Point()# vytvořili jsme instanci >>> p2.x = 8# vytvořili jsme atribut přiřazením hodnoty >>> p2.y# bez přiřazení atribut 'y' neexistuje! Traceback (most recent call last): File "<stdin>", line 1, in? AttributeError: Point instance has no attribute 'y' >>>
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 funci (metodě) se také říká konstruktor.
class Point: def __init__(self, x=0, y=0): 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.
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
Instanci můžeme zadat jako parametr obvyklým způsobem:
def
print_point(obj):
>>> print_point(g) 3, 4 >>> print_point(p) 0, 0
Řekněme, že chceme, aby třída představovala obdélník. 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 = hParametr 'posice' předpokládá zadání dvojice čísel - entici. Pro její vložení můžeme použít nepojmenovaný objekt již deklarované třídy Point (první definice v odst. 12.4):
>>> 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'.
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
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.4), Rectangle (odst 12.6) a funkcí find_center, print_point 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(), print_point(obj) (ods. 12.4) a find_center(obj) (odst. 12.7), protože všechny používají jména x,y
.
Bude nám chodit funkce grow_rectangle(obj,dwidth,dheight) (odst. 12.6).
Instanci lze reflektovat i uvnitř f-stringu (viz kap. 6.17 Formátování řetězců III). 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.16 Formátování řetězců II 'Jan Plíšek, věk 74. Překvapení!'viz soubor katalog.py
Poznámku
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.4:
>>> 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.6.
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)
Dekorace funkce funkcí je popsána v kapitole 3.11.
Při dekorování funkce třídou zastupuje klauzuru 'wrapper' speciální metoda __call__:
class Pohoda: def __init__(self, fun): 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
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.
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):# speciální metoda (instanční) 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
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é
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')
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.
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.
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'
|
|
|
|
|
|
|
|
|
r = Rectangle(Point(0,0), 50,100)
# test: r.area() --> 5000
r = Rectangle(Point(0,0), 50,100)
# test: r.perimeter() --> 300
Ke třídě Point (viz 12.4) 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).
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
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |