Lekce 20 - Magické metody v Pythonu
V předchozí lekci, Vlastnosti v Pythonu podruhé - Pokročilé vlastnosti a dědění, jsme se v práci s vlastnostmi zaměřili na dědění, časté chyby a vytváření vlastních dekorátorů pro vlastnosti.
V následujícím tutoriálu objektově orientovaného programování v Pythonu se zaměříme na magické (dunder) metody objektů. Znalost dunder metod nám poskytne hlubší pochopení toho, jak Python funguje "pod kapotou". Díky tomu budeme schopni lépe rozumět chování objektů a jejich interakcím s jazykovými konstrukty.
Magické metody
Magické (dunder) metody jsou klíčovou součástí Pythonu a tvoří
základ mnoha jeho OOP funkcí. Název "dunder" pochází z anglického "double
underscore" (dvojitá podtržítka), což odkazuje na jejich typický zápis,
například __init__()
nebo __str__()
. Tyto metody
byly do Pythonu přidány, aby umožnily vývojářům definovat
chování objektů v různých kontextech, jako je inicializace,
reprezentace, aritmetické operace a mnoho dalších.
S několika těmito metodami jsme se už setkali, jako například s magickou
metodou __init__()
pro inicializaci objektu nebo s metodou
__str__()
pro vypsání jeho lidsky čitelné reprezentace.
Přehled magických dunder metod
Dunder metody si pro lepší přehled rozdělíme do několika kategorií podle jejich funkcí:
1. Základní dunder metody
- objektová inicializace a reprezentace -
__init__()
,__str__()
,__repr__()
, - operátory porovnání -
__eq__()
,__lt__()
,__gt__()
, a další, - logické operace -
__bool__()
- matematické operátory -
__add__()
,__sub__()
,__mul__()
,__truediv__()
, a další, - správa kontextu -
__enter__()
,__exit__()
(pro použití vewith
příkazu).
2. Pokročilé dunder metody
- atributové a indexové operace -
__getattr__()
,__setattr__()
,__delattr__()
,__getitem__()
,__setitem__()
, - volání objektů -
__call__()
(přeměna objektu na volatelný objekt), - správa instancí -
__new__()
(používá se pro tvorbu nových instancí).
3. Speciální případy
- vytvoření uživatelsky definovaných kontejnerů -
__len__()
,__getitem__()
, - custom iterátory a generátory -
__iter__()
a__next__()
, - správa paměti a Garbage Collection -
__del__()
.
Toto je jen základní přehled. V této lekci se budeme věnovat detailně první kategorii. Předem upozorníme, že mnoho dunder metod je hluboce propojeno s kolekcemi, které zatím neznáme. Seznámíme se s nimi v kurzu Kolekce v Pythonu. Přesto se v příkladech kolekcím vyhneme, aby byly co nejvíce pochopitelné. Pojďme na to.
Základní dunder metody
Začneme přehledem a příklady použití základních metod.
Objektová inicializace a reprezentace
Tyto metody, které už dobře známe, jsou základním stavebním kamenem pro jakoukoliv třídu v Pythonu.
Metody __init__()
,
__str__()
a __repr__()
Metodu __init__()
Python volá, kdykoliv vytváříme novou
instanci třídy. Používá se k inicializaci atributů. Metody
__str__()
a __repr__()
určují, jak bude objekt
reprezentován jako řetězec. Zatímco __str__()
vrací čitelnou
reprezentaci pro koncové uživatele, __repr__()
má za cíl
poskytnout jednoznačnou reprezentaci objektu, která je často technické
povahy a vhodná pro vývojáře. Zde si příklady uvádět nebudeme, se všemi
zmíněnými metodami jsme se již detailně seznámili dříve.
Operátory porovnání
Na porovnávací metody se podívejme ve formě tabulky:
Metoda | Slovní popis | Reprezentuje kód |
---|---|---|
__lt__(self, other) |
menší než (less than) | x < y |
__le__(self, other) |
menší nebo roven (less or equal) | x <= y |
__eq__(self, other) |
rovná se (equal) | x == y |
__ne__(self, other) |
nerovná se (not equal) | x != y |
__gt__(self, other) |
větší než (greater than) | x > y |
__ge__(self, other) |
větší nebo roven (greater or equal) | x >= y |
Všechny porovnávací metody vrací True
nebo
False
, popř. vyvolají výjimku , pokud se porovnávání s
druhým objektem nepodporuje. Jejich syntaxe je stejná:
class Cislo:
def __init__(self, hodnota):
self.hodnota = hodnota
def __gt__(self, jine_cislo):
if isinstance(jine_cislo, Cislo):
return self.hodnota > jine_cislo.hodnota
else:
raise ValueError("Nelze porovnat 'Cislo' s objektem jiného typu.")
pi = Cislo(3.14)
e = Cislo(2.74)
print(pi > e) # volá pi.__gt__(e), e je "other"
# Příklad výjimky při pokusu porovnat 'Cislo' s 'str'
jina_hodnota = "text"
try:
print(pi > jina_hodnota) # vyvolá výjimku
except ValueError as error:
print(error) # vypíše chybovou zprávu
Logické operace
Metoda pro logické operace určuje, jak se má objekt chovat v kontextu pravdivostních testů a logických operací.
Metoda __bool__()
Podívejme se, jak dokážeme přizpůsobit chování metody při převodu
objektu na typ bool
:
class Auto:
def __init__(self, palivo):
self.palivo = palivo
def __bool__(self):
# Auto je "True" (funkční), pokud má více než 10 litrů paliva
return self.palivo > 10
# Testování
sluzebni_auto = Auto(5) # Málo paliva
moje_auto = Auto(20) # Dostatek paliva
print(bool(sluzebni_auto)) # Vypíše False
print(bool(moje_auto)) # Vypíše True
V příkladu metoda __bool__()
kontroluje, zda je v nádrži
více než 10
litrů paliva. Pokud ano, vrací True
,
jinak False
. Tím je dosaženo toho, že hodnota
bool()
na instanci třídy Auto
přímo závisí na
množství paliva v nádrži.
Tento přístup je užitečný v situacích, kdy chceme, aby hodnota
bool()
objektu reprezentovala jeho stav podle nějakého
specifického kritéria, které je důležité pro logiku našeho programu.
Matematické operátory
Matematické operátory v Pythonu jsou skvělým příkladem toho, jak dunder metody umožňují objektům reagovat na standardní matematické operace. Podíváme se na ně opět ve formě tabulky:
Metoda | Slovní popis | Reprezentuje kód |
---|---|---|
__add__(self, other) |
sčítání | + |
__sub__(self, other) |
odčítání | - |
__mul__(self, other) |
násobení | * |
__truediv__(self, other) |
dělení | / |
__eq__(self, other) |
rovná se | = |
__mod__(self, other) |
modulo | % |
__pow__(self, other[, modulo]) |
umocňování | ** |
Tyto metody umožňují objektům pracovat s aritmetickými operátory
stejně, jako by to byly běžné číselné typy. Jako příklad si definujeme
třídu KomplexniCislo
, která bude reprezentovat komplexní
čísla a bude implementovat součet:
class KomplexniCislo:
def __init__(self, realna, imaginarni):
self.realna = realna
self.imaginarni = imaginarni
def __add__(self, other):
return KomplexniCislo(self.realna + other.realna, self.imaginarni + other.imaginarni)
def __str__(self):
return f"{self.realna} + {self.imaginarni}i"
# Testování
prvni_komplex = KomplexniCislo(1, 2)
druhy_komplex = KomplexniCislo(3, 4)
vysledek = prvni_komplex + druhy_komplex
print(vysledek) # Vypíše "4 + 6i"
Všechny zmíněné metody umožňují přetížit chování standardních operátorů a přizpůsobit jejich funkčnost pro uživatelsky definované objekty.
Přetížení operátorů
Python nám umožňuje modifikovat způsob, jakým standardní operátory
(jako +
, -
, *
, /
,
<
, ==
a další) fungují s našimi vlastními
objekty. Už jsme si ukázali, že pro každý operátor existuje
odpovídající metoda. Teď si ukážeme, jak ji definovat v rámci třídy,
aby se změnila standardní funkcionalita daného operátoru.
Vytvoříme si třídu Vektor
, která reprezentuje 2D vektor a
přetěžuje některé matematické operátory:
class Vektor:
def __init__(self, souradnice_x, souradnice_y):
self.souradnice_x = souradnice_x
self.souradnice_y = souradnice_y
def __repr__(self):
return f"Vektor({self.souradnice_x}, {self.souradnice_y})"
def __add__(self, dalsi_vektor):
# Přetížení operátoru +
return Vektor(self.souradnice_x + dalsi_vektor.souradnice_x, self.souradnice_y + dalsi_vektor.souradnice_y)
def __sub__(self, dalsi_vektor):
# Přetížení operátoru -
return Vektor(self.souradnice_x - dalsi_vektor.souradnice_x, self.souradnice_y - dalsi_vektor.souradnice_y)
def __mul__(self, skalarni_hodnota):
# Přetížení operátoru * pro násobení skalárem
return Vektor(self.souradnice_x * skalarni_hodnota, self.souradnice_y * skalarni_hodnota)
# Příklad použití
vektor1 = Vektor(2, 3)
vektor2 = Vektor(1, 1)
print(vektor1 + vektor2) # Výstup: Vektor(3, 4)
print(vektor1 - vektor2) # Výstup: Vektor(1, 2)
print(vektor1 * 3) # Výstup: Vektor(6, 9)
Správa kontextu
V Pythonu se správa kontextu využívá hlavně pro správné zpracování
zdrojů a pro zajištění, že jsou tyto zdroje korektně uvolněny nebo
uzavřeny po použití. Klíčovým prvkem pro správu kontextu je využití
příkazu with
, který automaticky zajistí správné otevření a
uzavření zdrojů, jako jsou soubory, síťová připojení a podobně. Tato
kapitola je zatím trochu mimo naše obzory, soubory a sítěmi se budeme
zabývat v pokročilejších kurzech. Přesto ve výčtu nesmí chybět a
příklad, který si uvedeme, bude zcela v mezích našich znalostí.
Metoda __enter__()
Metoda __enter__()
se zavolá v okamžiku, kdy kód vstupuje do
kontextového manažera – tedy do bloku kódu definovaného příkazem
with
. Typicky se v této metodě provádějí inicializační
operace, jako je otevření souboru nebo získání zámku.
Metoda __exit__()
Metoda __exit__()
se zavolá na konci bloku kódu v příkazu
with
, bez ohledu na to, zda byl blok opuštěn normálně, nebo
došlo k vyhození výjimky. Tato metoda obvykle zahrnuje úklidové operace,
jako je zavírání souborů nebo uvolnění zdrojů. Má tři argumenty, které
reprezentují typ výjimky, hodnotu výjimky a traceback, pokud došlo k
vyhození výjimky.
Podívejme se na příklad:
import time
class Casovac:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, traceback):
self.end = time.time()
print(f"Doba trvání: {self.end - self.start} sekund.")
# Použití:
with Casovac() as t:
# Kód, který chceme změřit:
for i in range(1000000):
i *= 2
# Po ukončení bloku 'with' se vypíše doba trvání
Rozhodně nejde o kompletní výčet všech dunder metod v daných kategoriích. To by jednak lekce extrémně nabyla na objemu a také by se podobala spíše encyklopedii. Pro hlubší studium je tu dokumentace Pythonu, kde je výčet opravdu komplexní. To je pro tuto lekci vše
V příští lekci, Magické metody v Pythonu podruhé, se podíváme na další, pokročilejší magické metody objektů.