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 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 na statiku - Objektově orientované programování v PythonuPOZOR! 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:

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é:

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 544x (941 B)
Aplikace je včetně zdrojových kódů v jazyce Python

 

Předchozí článek
Řešené úlohy k 8.-11. lekci OOP v Pythonu
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Statika v Pythonu podruhé - Statické a třídní metody
Článek pro vás napsal gcx11
Avatar
Uživatelské hodnocení:
334 hlasů
(^_^)
Aktivity