IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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í ve with 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ů.


 

Předchozí článek
Vlastnosti v Pythonu podruhé - Pokročilé vlastnosti a dědění
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Magické metody v Pythonu podruhé
Článek pro vás napsal gcx11
Avatar
Uživatelské hodnocení:
301 hlasů
(^_^)
Aktivity