Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 13 - Statika v Pythonu podruhé - Statické a třídní metody

V minulé lekci, Statika v Pythonu - Třídní atributy, jsme si vysvětlili statiku v Pythonu.

V tomto tutoriálu objektově orientovaného programování v Pythonu budeme pokračovat v práci se statikou. Vysvětlíme si statické a třídní metody.

Statické metody

Statické metody se podobně jako třídní atributy volají na třídě. Jsou to metody, které sice patří třídě, ale nejsou svázány s konkrétní instancí. Tato vlastnost z nich činí ideální kandidáty pro pomocné metody, které jsou často potřeba, ale nevyžadují kontext jednotlivých instancí.

Podívejme se opět na reálný příklad. Při registraci nového uživatele potřebujeme znát minimální délku hesla ještě před jeho vytvořením. Bylo by také dobré, kdybychom mohli před jeho vytvořením i heslo zkontrolovat, zda má správnou délku, neobsahuje diakritiku, je v něm alespoň jedno číslo a podobně. Za tímto účelem si vytvoříme pomocnou statickou metodu zvaliduj_heslo(), kterou si zavoláme na třídě Uzivatel. Statická metoda zvaliduj_heslo() v třídě Uzivatel může přistupovat k třídnímu atributu minimalni_delka_hesla pomocí názvu třídy, což demonstruje, jak statická metoda pracuje s třídními daty bez nutnosti vytvářet instanci třídy:

class Uzivatel:

    minimalni_delka_hesla = 6

    def __init__(self, jmeno, heslo):
        self._jmeno = jmeno
        self._heslo = heslo
        self._prihlaseny = False

    @staticmethod
    def zvaliduj_heslo(heslo):
        if len(heslo) >= Uzivatel.minimalni_delka_hesla:  #  metoda přistupuje k třídnímu atributu přes název třídy, ne instance.
            return True
        else:
            return False

print(Uzivatel.zvaliduj_heslo("heslojeveslo"))

Pozor! Vzhledem k tomu, že zvaliduj_heslo() je statická metoda, nelze v ní přistupovat k instančním atributům třídy Uzivatel. Instanční atributy, jako je například _jmeno, jsou spojené s konkrétní instancí a statická metoda operuje nezávisle na jakékoli instanci. Statické metody mohou přistupovat pouze k třídním atributům, které jsou definovány na úrovni třídy a jsou stejné pro všechny instance. To znamená, že v rámci zvaliduj_heslo() můžeme pracovat s třídním atributem minimalni_delka_hesla, ale není možné zde přistupovat k instančním atributům jako je _jmeno.

Dekorátor @staticmethod

Shrňme si znalosti, které jsme zatím získali a povězme si několik detailů k použitému dekorátoru. Dekorátor @staticmethod se v Pythonu používá k označení metody třídy, která nevyžaduje referenci na konkrétní instanci třídy (self) ani na samotnou třídu (cls - více si řekneme za chvíli). Jinými slovy, statická metoda pracuje nezávisle na instancích třídy a její chování se nemění na základě stavu instance nebo třídy.

Dekorátory v Pythonu představují způsob, jak modifikovat nebo rozšiřovat funkčnost funkcí nebo tříd bez nutnosti měnit jejich původní kód. Kromě uživatelsky definovaných dekorátorů Python nabízí i řadu vestavěných dekorátorů, které usnadňují běžné úkoly v objektově orientovaném programování, například práci s třídními nebo statickými metodami. O dekorátorech si podrobně povíme později v kurzu.

Hlavní výhody použití @staticmethod:

  • zřejmost - když vidíme v kódu dekorátor @staticmethod, je jasné, že tato metoda nezávisí na stavu instance ani třídy,
  • výkon - protože @staticmethod nevyžaduje předání self nebo cls, bývá volání této metody o něco rychlejší.

Kdy @staticmethod používáme

Statickou metodu tedy použijeme, když:

  • metoda nevyžaduje přístup k žádným atributům nebo metodám třídy,
  • metoda nevyžaduje změnu v třídě nebo jejích instancích,
  • máme logickou funkci, která by se vhodně hodila do třídy (např. kvůli organizaci kódu), ale nevyužívá konkrétní vlastnosti třídy.

Ukažme si příklad. Mějme třídu Geometrie, která bude obsahovat několik statických metod spojených s geometrickými výpočty:

import math

class Geometrie:

    @staticmethod
    def obvod_kruhu(polomer):
        return round((2 * math.pi * polomer), 2)

    @staticmethod
    def obvod_obdelnika(delka, sirka):
        return 2 * (delka + sirka)

    @staticmethod
    def je_ctverec(delka, sirka):
        return delka == sirka

# Použití statických metod
print(Geometrie.obvod_kruhu(5))
print(Geometrie.obvod_obdelnika(5, 3))
print(Geometrie.je_ctverec(5, 5))
print(Geometrie.je_ctverec(5, 3))

Ve výstupu konzole uvidíme:

Použití statických metod:
31.42
16
True
False

Proč vůbec @staticmethod používáme

V Pythonu je sice technicky možné definovat metody uvnitř třídy bez použití @staticmethod dekorátoru, ale pro jeho použití máme několik pádných argumentů:

  • zřejmost - Už jsme si řekli, že dekorátor @staticmethod jasně říká, že metoda nemá přístup k instančním atributům/metodám. Když tento dekorátor jiní vývojáři vidí, okamžitě vědí, co od té metody mohou očekávat.
  • organizace a design - Použití @staticmethod nám pomáhá při organizaci kódu.
  • rozšířitelnost - Pokud se později rozhodneme, že chceme přidat nějaké třídní nebo instanční atributy/metody, které by mohly interagovat s našimi statickými metodami, je mnohem jednodušší a čistší mít již strukturu, která rozlišuje mezi statickými a ne-statickými metodami.
  • předávání self a cls - Když definujeme metodu v třídě bez @staticmethod, první argument je automaticky považován za referenci na instanci (self) nebo na třídu (cls), pokud je to třídní metoda (o těch si řekneme za chvíli). Snadno tak narazíme na problémy s nesprávným počtem argumentů při volání funkce:
class Geometrie:
    def obvod_kruhu(polomer):  # metoda BEZ dekorátoru
        return 2 * math.pi * polomer

print(Geometrie.obvod_kruhu(5)) # Funguje správně - i když metoda nemá dekorátor, volání přímo na třídě je možné
g = Geometrie()
print(g.obvod_kruhu(5))         # Způsobí chybu

Ve výstupu konzole uvidíme:

Použití statických metod bez dekorátoru:
31.41592653589793
...
TypeError: Geometrie.obvod_kruhu() takes 1 positional argument but 2 were given

Dekorátor @staticmethod tedy umožňuje metodu volat jak skrze třídu, tak skrze instanci, aniž by bylo nutné dodávat self:

class Geometrie:
    @staticmethod
    def obvod_kruhu(polomer):
        return 2 * math.pi * polomer

print(Geometrie.obvod_kruhu(5))  # Funguje správně
g = Geometrie()
print(g.obvod_kruhu(5))         # Také funguje správně

Zkrátka, i když je možné vytvářet "statické" metody v Pythonu bez použití @staticmethod, dekorátor nám poskytuje mnohem větší flexibilitu, čitelnost a robustnost při práci s třídami.

Utility (helper) třídy

Když se zamyslíme nad tím, co jsme si dosud řekli, vyplývá nám jedno zajímavé zjištění. Python nám umožňuje vytvářet jakési kontejnery (obvykle souvisejících) metod, sdružených v jedné třídě. Takové třídě se říká utility třída (nebo také helper třída). Jejich hlavní funkcí je uspořádat soubor souvisejících funkcí do jedné logické jednotky, což nám pomáhá k lepší organizaci kódu a lepší čitelnosti. Za příklad nám poslouží naše třída Geometrie, ovšem bez dekorátoru @staticmethod a s upraveným názvem GeometrieUtilities popisujícím její účel:

class GeometrieUtilities:

    def obvod_kruhu(polomer):
        return round((2 * math.pi * polomer), 2)

    def obvod_obdelnika(delka, sirka):
        return 2 * (delka + sirka)

    def je_ctverec(delka, sirka):
        return delka == sirka

print(GeometrieUtilities.obvod_kruhu(5))

Tímto způsobem si tedy umíme vytvořit kontejner metod, které jsou na první pohled statické a je jen třeba pamatovat na to, že taková utility třída neslouží k vytváření instancí, ale spíše jako sada nástrojů, nebo přímo knihovna. Ačkoliv neexistuje pevná konvence pro pojmenování tohoto typu tříd, doporučujeme v názvu zohlednit jejich podstatu. Tak, jako jsme učinili v našem příkladu s třídou GeometrieUtilities. Mnoho standardních knihoven v Pythonu obsahuje moduly, které v podstatě fungují jako utility třídy, protože poskytují sady funkcí či metod bez potřeby vytvářet instance.

Pozor! Utility třídy neinstancujeme. Volání metody utility třídy z její instance způsobí chybu.

Třídní metody

Python obsahuje kromě statických i třídní metody. A zde se konečně dostáváme k onomu tajemnému cls :-) První parametr třídní metody vždy obsahuje odkaz na třídu a podle konvencí se pojmenovává cls. Za pomoci tohoto parametru potom voláme třídní atributy, podobně jako se self. Třídní metody pracují s třídními atributy a ne s instančními atributy. Označujeme je pomocí dekorátoru @classmethod. Třídní metody se hodí v tom případě, že budeme třídu dědit a chceme mít v potomkovi jinou hodnotu třídního atributu. Jinak je lepší použít statickou metodu. Nejlepší bude, když si ukážeme příklad:

class Rodic:
    hodnota = "rodič"

    @classmethod
    def vrat_hodnotu(cls):  # cls odkazuje na aktuální třídu (buď Rodic nebo Potomek, pokud je tato metoda volána z potomka)
        return cls.hodnota

    @staticmethod
    def staticka_metoda():
        return "Jsem statická metoda, zůstanu stejná i pokud mě někdo zdědí."

class Potomek(Rodic):
    hodnota = "Jsem potomek, ne rodič."

print(Potomek.vrat_hodnotu())
print(Potomek.staticka_metoda())

V konzoli uvidíme:

Třídní metody:
Jsem potomek, ne rodič.
Jsem statická metoda, zůstanu stejná i pokud mě někdo zdědí.

V našem příkladu voláme třídní metodu vrat_hodnotu() na potomkovi. Metoda nám vrátí hodnotu atributu hodnota z třídy Potomek, nikoliv z třídy Rodic. Ale pozor, staticka_metoda() nemá žádný přístup k třídním atributům, takže nezáleží na tom, jestli je volána z rodiče či potomka – vždy bude vracet stejný výstup.

Rozdíly mezi třídními a statickými metodami

Podívejme se stručný přehled rozdílů mezi třídními a statickými metodami:

1. Dekorátor:

  • Třídní metoda používá dekorátor @classmethod.
  • Statická metoda používá dekorátor @staticmethod.

2. První parametr:

  • Třídní metoda - prvním parametrem je vždy odkaz na třídu, obvykle pojmenovaný cls.
  • Statická metoda - nemá žádný speciální první parametr. Chová se jako běžná funkce, která je pouze definována uvnitř třídy.

3. Dědičnost:

  • Třídní metoda - pokud je třída děděná, třídní metoda v potomcích bude pracovat s třídními atributy daného potomka,
  • Statická metoda - je nezávislá na dědění. Ať je volaná z rodičovské třídy nebo z potomka, chová se vždy stejně.

4. Použití:

  • Třídní metoda - je užitečná, když potřebujeme pracovat s třídními atributy nebo když chceme, aby metoda byla upravitelná v děděných třídách.
  • Statická metoda - je vhodná, když potřebujeme provést nějakou operaci, která souvisí s třídou, ale nevyžaduje si přístup k jejím atributům. Je to v podstatě funkce, která nesouvisí s konkrétní instancí třídy.

Upravme si na závěr třídu Uzivatel, která nás provází od počátku tématu statiky:

class Uzivatel:
    minimalni_delka_hesla = 6  # třídní atribut

    def __init__(self, jmeno, heslo):
        self._jmeno = jmeno
        self._heslo = heslo

    @staticmethod
    def zvaliduj_heslo(heslo):
        return len(heslo) >= Uzivatel.minimalni_delka_hesla

    @classmethod
    def nastav_minimalni_delka_hesla(cls, nova_delka):
        cls.minimalni_delka_hesla = nova_delka

    def je_heslo_validni(self):
        return self.zvaliduj_heslo(self._heslo)

class VIPUzivatel(Uzivatel):
    minimalni_delka_hesla = 10  # VIP uživatelé mají delší minimální délku hesla

    @staticmethod
    def zvaliduj_heslo(heslo):
        return len(heslo) >= VIPUzivatel.minimalni_delka_hesla
    @classmethod
    def informace_o_hesle(cls):
        return f"Minimální délka hesla pro {cls.__name__} je {cls.minimalni_delka_hesla} znaků."

tomas = Uzivatel('Tomáš', 'motyka')
petr = VIPUzivatel('Petr', 'veslo12')

print(tomas.zvaliduj_heslo('test'))  # Výstup: False - heslo je krátké

print(petr.informace_o_hesle())  # Výstup: Minimální délka hesla pro VIPUzivatel je 10 znaků.

print(tomas.je_heslo_validni())  # Výstup: True
print(petr.je_heslo_validni())   # Výstup: False

V příkladu máme metody:

  • zvaliduj_heslo() - statická metoda a lze ji volat i přes instanci, ale její logika závisí pouze na konkrétní třídě Uzivatel, nikoli na tom, jaká instance (běžný uživatel nebo VIP) ji volá. Metoda je pro VIP uživatele přetížená kvůli délce hesla.
  • informace_o_hesle() - třídní metoda v třídě VIPUzivatel a když ji voláme přes instanci, vrací informace specifické pro třídu té instance (VIPUzivatel v našem případě).
  • je_heslo_validni() - metoda instance, která využívá statickou metodu zvaliduj_heslo(). Demonstruje, jak metoda instance dokáže využívat jak statické, tak třídní metody.

To je pro tuto lekci vše.

V následujícím kvízu, Kvíz - Statika v Pythonu, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 20x (1.08 kB)
Aplikace je včetně zdrojových kódů v jazyce Python

 

Předchozí článek
Statika v Pythonu - Třídní atributy
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Kvíz - Statika v Pythonu
Článek pro vás napsal Karel Zaoral
Avatar
Uživatelské hodnocení:
114 hlasů
Karel Zaoral
Aktivity