Lekce 12 - Statika v Pythonu - Třídní atributy
V předešlém cvičení, Řešené úlohy k 8.-11. lekci OOP v Pythonu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V následujícím tutoriálu objektově orientovaného programování v Pythonu se budeme věnovat pojmu statika. Až doposud jsme byli zvyklí, že data (stav) nese instance. Proměnné (atributy), které jsme definovali, tedy patřily instanci a byly pro každou instanci jedinečné. OOP však umožňuje definovat atributy a metody na samotné třídě. Těmto prvkům říkáme statické (nebo také třídní) a jsou nezávislé na instanci. Nejprve se zaměříme na třídní atributy.
POZOR! Dnešní lekce vám ukáže statiku, tedy
postupy, které v podstatě narušují objektový model. OOP je obsahuje jen pro
speciální případy a obecně platí, že vše jde napsat bez
statiky. Vždy musíme pečlivě zvážit, zda statiku opravdu
nutně potřebujeme. Obecné doporučení je statiku vůbec
nepoužívat, pokud si nejsme naprosto jisti, co děláme. Podobně
jako globální proměnné je statika v objektovém programování něco, co
umožňuje psát špatný kód a porušovat dobré praktiky. Dnes si ji tedy
spíše vysvětlíme. Znalosti použijte s rozvahou, na světe bude potom méně
zla.
Třídní atributy
Jako třídní můžeme označit různé prvky. Začněme u atributů. Jak
jsme již v úvodu zmínili, statické prvky patří třídě, nikoli instanci.
Data v nich uložená tedy můžeme číst bez ohledu na to, zda nějaká
instance existuje. V podstatě můžeme říci, že třídní atributy jsou
sdílené mezi všemi instancemi třídy. Jsou definovány uvnitř třídy, ale
mimo jakoukoli metodu a existují i před vytvořením jakékoli instance
třídy. Vysvětlíme si to na příkladu. Představme si, že potřebujeme
sdílet nějaký údaj mezi všemi instancemi třídy nebo chceme, aby byl tento
údaj dostupný, i když ještě neexistuje žádná instance třídy. Založme
si nový soubor (název např. statika.py
) a udělejme si
jednoduchou třídu Uzivatel
:
class Uzivatel: def __init__(self, jmeno, heslo): self._jmeno = jmeno self._heslo = heslo self._prihlaseny = False def prihlas_se(self, zadane_jmeno, zadane_heslo): if self._jmeno == zadane_jmeno and self._heslo == zadane_heslo: self._prihlaseny = True return True else: self._prihlaseny = False return False # jméno nebo heslo nesouhlasí
Třída je poměrně jednoduchá. Reprezentuje uživatele nějakého
systému. Každá instance uživatele má své jméno, heslo a také se o ní
ví, zda je přihlášená či nikoli. Aby se uživatel přihlásil, zavolá se
na něm metoda prihlas_se()
. Ta nese v parametru jméno a heslo,
které člověk za klávesnicí zadal. Metoda ověří, zda se jedná opravdu o
tohoto uživatele a pokusí se ho přihlásit. Vrátí True/False
podle toho, zda přihlášení proběhlo úspěšně.
Jak do toho zapojíme třídní atribut? Když se nový uživatel registruje,
systém mu napíše, jakou minimální délku musí jeho heslo mít. Toto
číslo ale musíme mít někde uložené. Jenže ve chvíli, kdy
uživatele registrujeme, ještě nemáme jeho instanci k dispozici.
Objekt zkrátka není vytvořený a vytvoří se až na základě dat
získaných po vyplnění formuláře. Samozřejmě by bylo velmi přínosné,
kdybychom měli údaj o minimální délce hesla uložený ve třídě
Uzivatel
, protože k němu logicky patří. Jak to tedy
vyřešíme? Údaj uložíme přímo ve třídě Uzivatel
do
třídního atributu. Vytvoříme si k tomu atribut
minimalni_delka_hesla
:
class Uzivatel: minimalni_delka_hesla = 6 def __init__(self, jmeno, heslo): ... def prihlas_se(self, zadane_jmeno, zadane_heslo): ...
Až dosud jsme všechna data objektu přidávali až při vzniku jeho instance pomocí konstruktoru. Statika nám poskytuje řešení, jak objekt vybavit daty ještě předtím, než vůbec vznikne jakákoliv jeho instance.
Pojďme si atribut vypsat. K třídnímu atributu přistoupíme přímo přes
třídu, syntaxe je NazevTridy.nazev_atributu
:
class Uzivatel:
minimalni_delka_hesla = 6
def __init__(self, jmeno, heslo):
self._jmeno = jmeno
self._heslo = heslo
print(Uzivatel.minimalni_delka_hesla) # všimneme si velkého písmena v názvu třídy - skutečně tedy nejde o instanci
Vidíme, že atribut opravdu náleží třídě. Můžeme se na něj ptát v různých místech programu bez toho, aniž bychom měli uživatele vytvořeného. Ale pozor, na instanci uživatele tento atribut nalezneme také:
class Uzivatel:
minimalni_delka_hesla = 6
def __init__(self, jmeno, heslo):
self._jmeno = jmeno
self._heslo = heslo
novy_uzivatel = Uzivatel("Tomáš Marný", "heslojeveslo")
print(novy_uzivatel.minimalni_delka_hesla) # malé "n" v novy_uzivatel značí, že skutečně pracujeme s instancí
Vidíme tedy, že třídní atributy sdílí své hodnoty napříč všemi instancemi dané třídy. Ale pozor! Při změně třídního atributu v instanci změníme pouze hodnotu pro danou instanci.
Podívejme se na příklad:
class Trida: tridni_atribut = 'Zde jsou data třídního atributu, která jsou dostupná kdykoliv bez vytvoření instance.' instance = Trida() print(instance.tridni_atribut) instance.tridni_atribut = 'Zde měníme hodnotu třídního atributu v instanci.' print(instance.tridni_atribut) print(Trida.tridni_atribut)
Ve výstupu konzole uvidíme:
Třídní atribut:
Zde jsou data třídního atributu, která jsou stále dostupná kdykoliv bez vytvoření instance.
Zde měníme hodnotu třídního atributu v instanci.
Zde jsou data třídního atributu, která jsou stále dostupná kdykoliv bez vytvoření instance.
Jako další praktické využití třídních atributů se nabízí
číslování uživatelů. Budeme chtít, aby měl každý uživatel
přidělené unikátní identifikační číslo. Bez znalosti statiky bychom si
museli hlídat zvenčí každé vytvoření nového uživatele a počítat je.
My si však vytvoříme přímo na třídě Uzivatel
statický (=
třídní) atribut dalsi_id
, kde bude vždy připraveno číslo pro
dalšího uživatele. První uživatel bude mít id = 1
, druhý
2
a tak dále. Uživateli tedy přibude nový atribut
id
, který se v konstruktoru nastaví podle hodnoty
dalsi_id
. Pojďme si to vyzkoušet:
class Uzivatel:
minimalni_delka_hesla = 6 # třídní atribut
dalsi_id = 1 # třídní atribut
def __init__(self, jmeno, heslo):
self._jmeno = jmeno
self._heslo = heslo
self._prihlaseny = False
self._id = Uzivatel.dalsi_id # přidělíme aktuální id
Uzivatel.dalsi_id += 1 # připravíme id pro další instanci
uzivatel_admin = Uzivatel("Tomáš Správce", "adminheslo")
uzivatel_obycejny = Uzivatel("Tomáš Uživatel", "heslouzivatele")
print(f"ID uživatele {uzivatel_admin._jmeno} je {uzivatel_admin._id}")
print(f"ID uživatele {uzivatel_obycejny._jmeno} je {uzivatel_obycejny._id}")
Třída si sama ukládá, jaké bude id
další její instance.
Toto id
přiřadíme nové instanci v konstruktoru a zvýšíme ho
o 1
, aby bylo připraveno pro další instanci.
Specifika dynamicky typovaného jazyka
Python je dynamicky typovaný jazyk. To znamená, že jeho možnosti jsou oproti statickým jazykům (typicky C#) poněkud širší. Podívejme se na konkrétní příklady.
Dynamické přiřazení v Pythonu
V Pythonu umíme vytvořit třídní atributy za běhu. Jde o stejný mechanismus, jaký jsme si popsali v lekci Zapouzdření atributů podrobně v Pythonu. Tedy i když neexistovaly při definici třídy. To jak už víme je něco, co v jazycích s pevnými definicemi tříd, jako je C#, není možné. Podívejme se na příklad:
class Trida: pass # nějaký kód programu # zjistili jsme, že by se nám hodil třídní atribut. Tak si jej vytvoříme: Trida.novy_tridni_atribut = "Toto je nový třídní atribut!"
Přetypování
Toto je sice zřejmé z povahy Pythonu, ale stejně se o typování zmíníme. Typ třídního atributu lze snadno změnit za běhu:
class Clovek:
vek = 30
print(f"Původní věk: {Clovek.vek} (typ {type(Clovek.vek).__name__})")
# Nyní změníme typ třídního atributu 'vek' z čísla na řetězec
Clovek.vek = "Třicet let"
print(f"Po přetypování: {Clovek.vek} (typ {type(Clovek.vek).__name__})")
Funkce type()
vrací třídu (nebo typ) objektu, to
už známe. Když ale chceme získat pouze jméno třídy jako řetězec (v
našem příkladu int
), použijeme syntaxi s magickým atributem
.__name__
. O "magii" v Pythonu si povíme později v kurzu.
Přetypování je v některých případech užitečné (například když
potřebujeme upravit chování objektu za běhu), ale může také vést k
chybám. Typicky pokud nečekáme (zapomeneme), že se typ atributu změnil v
průběhu životního cyklu programu a pokusíme se s ním pracovat jako s
původním typem int
.
To je pro tuto lekci vše.
V příští lekci, Statika v Pythonu podruhé - Statické a třídní metody, dokončíme téma statiky. Probereme statické a třídní metody.
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 609x (941 B)
Aplikace je včetně zdrojových kódů v jazyce Python