Python je programovací jazyk, který podporuje
Slovem vše je míněno doslova vše: hodnoty, funkce, třídy, metody, dokonce i zdrojový kód, zvaný
Prvotním generátorem objektů jsou
S řadou vestavěných tříd (
Termíny
Nejjednodušší deklarace třídy vypadá takto:
class Emanuel :'''Nepovinný dokumentační řetězec - docstring ''' pass
Záhlaví složené z klíčového slova
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
Následuje odsazené tělo třídy, které bude obsahovat řadu dalších objektů. V naší demonstraci jsme uvedli příklad
>>> Emanuel.__doc__'Nepovinný dokumentační řetězec - docstring'
S naší prostinkou třídou můžeme už provádět řadu regulérních úkonů, například:
>>> eman = Emanuel()
>>> Emanuel.strom = "ořech"
>>> eman.míra = 158
Definice třídy se mohou vyskytnout kdekoli v programu, ale obvykle se umisťují poblíž počátku (po
příkazech
Konstrukci třídy si dále vysvětlíme na konkretním případě třídy
Abychom při každé deklaraci objektu nemuseli vytvářet jeho datové atributy ručně, použijeme speciální metodu
class Point :def __init__ (self, x=0, y=0):# konstruktor self.x = x self.y = y
Prvním parametrem definice
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
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 = ydef 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ě
>>> 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
V popisované deklaraci třídy Point jsou uvedená jména nezávazná. Stejně dobře nám poslouží třída, deklarovaná takto:
class Point :def __init__ (nerv, x=0, y=0):# konstruktor nerv.a = x nerv.b = y
V naší 'odchylce' zastupuje slovo 'nerv' jméno budoucí možné instance. Jména 'x', 'y' jsou interní označení pro parametry metody __init__, které se coby atributy instance budou nazývat 'a', 'b'.
>>> p = Point()# nerv = p >>> p.a, p.b# x, y = a, b (0, 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
Kromě dokumentačního řetězce a klíčového slova "pass" může třída obsahovat další dva důležité objekty - datové a funkční.
Datovými objekty (atributy) jsou proměnné, funkčními objekty jsou metody, což jsou funkce, definované uvnitř třídy a volané pro instanci (objekt) tečkovou notací.
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ř.
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 p.y >>> del Point.a >>> del p
Metoda je funkce, deklarovaná uvnitř třídy.
Pro třídu v Pythonu se rozlišují tři metody:
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.
Ř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.
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 (viz 10.2.2):class Rectangle :def __init__ (self, posice, w, h): self.corner = posice self.width = w self.height = h
>>> 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
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
def find_center (obj): p = Point() p.x = obj.corner.x + obj.width/2.0 p.y = obj.corner.y + obj.height/2.0return p
Když si definice tříd
>>> 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
>>> boot.corner (40,60) >>> boot.corner.xAttributeError: 'tuple' object has no attribute 'x'
Pro takto deklarovaný objekt nám samozřejmě nebudou chodit funkce
Instanci lze reflektovat i uvnitř f-stringu (viz kap. 6.8.3). Máme-li například definovanou třídu
# F:/codetest/howto/ch-10/katalog.py class Komik :def __init__ (self, first_name, last_name, age): self.first_name = first_name self.last_name = last_name self.age = agedef __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
>>>Poznámka 1import katalog# importujeme deklaraci třídy >>> komik = katalog.Komik("Jan", "Plíšek", "74")# instance třídy >>> f"{komik}" (případně f"{komik!s}" ) 'Jan Plíšek, věk 74.' >>> f"{komik!r}"# viz 6.10.3 Formátování literálem (f-stringem) 'Jan Plíšek, věk 74. Překvapení!'# viz soubor katalog.py (2)
Zmíněnou "interaktivní konzolu" si vytvoříme tak, že se v systémové konzole (cmd, cmder, yori, terminal, bash) nacédujeme do složky "F:/codetest/howto/ch-10", kde si otevřeme konzolu Pythonu příkazem "python". Zde zadáváme výše uvedené příkazy.
Poznámka 2Dodatek "Překvapení!" jsme při uplatnění konverze
Při kopírování třídy či objektu třídy (instance) použijeme vždy metodu
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
>>>import copy >>> p1 = Point(3,4) >>> p2 = copy.copy(p1) >>> p1is 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
Vytvoříme-li obdélník
>>> b1 = Rectangle(Point(), 100, 200) >>> b2 = copy.copy(b1)vytvoří se nezávislé kopie pouze atributů
Nezávislou hlubokou kopii (deep copy) i vnořených objektů vytvoříme metodou
>>> b2 = copy.deepcopy(b1)
Iterátorem v tomto případě je instance třídy s definovanými metodami
Jako příklad si uvedeme skript, který si poté pustíme v konzole:
class Count :def __init__ (self, m): self.m = m-1 self.n = 0def __iter__ (self):return selfdef __next__ (self):if self.n <= self.m: cur, self.n = self.n, self.n + 1return curelse :raise StopIteration()
>>> cnt = Count(5)# instance třídy a zároveň iterátor >>> next(cnt)# první iterace 0 >>> for i in cnt: print(i, end=" ") 1 2 3 4# opakovaná iterace pro zbytek členů >>> for in cnt: print(i, end=" ") ...# zdvořilé mlčení, iterátor je prázdný
Alternativa funkce
class Squares (object):def __init__ (self, start, stop): self.start = start self.stop = stopdef __iter__ (self):return selfdef __next__ (self):if self.start >= self.stop:raise StopIteration current = self.start * self.start self.start += 1return current
>>> sqr = Squares(4,8)# instance třídy a zároveň iterátor >>> >>> for i in sqr: print(i, end=" ") 16 25 36 49# dychtivá (eager) iterace >>> for in sqr: print(i, end=" ") ...# zdvořilé mlčení, iterátor je prázdný
Dekorátor je funkce vyššího řádu, která jako argument přijímá dekorovanou funkci a vrací svou vnitřní funkci (
Úč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 ():func ()# interní volání dekorované funkce return wrapper Dekorovaná funkce:@decorator # jedna varianta def decorated_function ():#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:
# 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
decorated_function = decorator(decorated_function)nebo třeba: ovečka = decorator(decorated_function)
Invokací
>>> 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 ():
>>> 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 ():return wrapper def lines (ovce):# dekorátorová funkce def wrapper():return wrapper @decorator @lines def beran ():# dekorovaná funkce
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
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: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)
def logging (func):def wrapper (*args, **kwargs):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
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.
Vlastní dekorátorová 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
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
Systémový dekorátor
from functools import wraps def counter (func): @wraps(func)# dekorace importovaným dekorátorem def wrapper (*args, **kwargs): wrapper.num_calls += 1# počítadlo f "Call {wrapper.num_calls} of {func.__name__!r }:")# viz Pozn. return func(*args, **kwargs) wrapper.num_calls = 0return wrapper @counter# dekorace vlastní funkcí def add (x, y): "Aplikován dekorátor 'wraps'"
>>> 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
Při dekorování funkce třídou zastupuje klauzuru
class Pohoda :def __init__ (self, fun):# konstruktor self.fun = fundef __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
>>> 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 = xdef __call__ (self, a):# výraz @Decoratorself.x(a) je invokace ovečkydef square (a):return a*a
>>> square(5) Druhá mocnina 5 je 25
import functoolsimport timedef 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() - startreturn valreturn wrapper @timerclass Calculator :def __init__(self, num): self.num = numimport 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)])
>>> c = Calculator(100) Finished 'Calculator' in 2.0111 secs >>> c.double_and_add() Result : 9900
Dekorátor
Speciální (také
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".__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 = ydef __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__ 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
Tento dekorátor automaticky generuje všechny potřebné
from dataclassesimport dataclass @dataclassclass Kontakt : name: str# jméno a typ pole email: str# jméno a typ pole phone: str = "00 00 00 00"# dtto plus hodnota pole
Vytvoření instance třídy může mít následující formu:
>>> contact = Kontakt("test", "test@test.com", '72 63 08 56') >>> contact Kontakt(name='test', email='test@test.com', phone='72 63 08 56')
Implicitní hodnotu lze při invokaci vynechat:
>>> contact = Kontakt(name="test1",email="test1@test.com") >>> contact Kontakt(name='test1', email='test1@test.com', phone='00 00 00 00')
Výpis vytvořených metod a funkcí pro instanci
>>> print(dir(contact))
Výpis polí instance
>>> print(contact.__dict__) {'name': 'test', 'email': 'test@test.com', 'phone': '72 63 08 56'}
Implicitní hodnotu argumentu lze vytvořit rovněž importovanou metodou:
from dataclassesimport dataclassfrom datetimeimport datetime @dataclassclass Kontakt1 : name: str email: str time: datetime.date = datetime.now().date()
>>> contact = Kontakt1("name = Pavel","email = best@test.cz") >>> print(contact) Kontakt1(name='Pavel', email='best@test.cz', time=datetime.date(2024, 1, 7))
Implicitní 'dunder' metody lze ručně upravovat jejich zneplatněním při deklaraci dekorátoru a opětovnou deklarací metody v těle třídy:
from dataclassesimport dataclassimport time @dataclass(repr = False)class Kontakt2 : name: str email: str seconds = time.time()# pomocný argument local_time = time.ctime(seconds)def __repr__ (self):return (f"{self.name}, {self.email}, {self.local_time}")
>>> contact = Kontakt2(name = "Pavel", email = "best@test.cz") >>> print(contact) Pavel, best@test.cz, Tue Jan 9 17:16:16 2024
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, potomek) přebírá atributy a metody třídy výchozí, neboli bázové (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"class Pes(Savci) :def __init__ (self): super().__init__('Pes')Varianta: Savci.__init__(self, 'Pes')
Speciální metoda
>>> 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):class B(A) : pes = "Voříšek" def zobraz(self):
>>> obj = B() >>> obj.pes 'Voříšek' >>> obj.zobraz() Jsem třída B.
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(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