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
nebocls
, 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
acls
- 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 metoduzvaliduj_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 41x (1.08 kB)
Aplikace je včetně zdrojových kódů v jazyce Python