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 18 - Vlastnosti v Pythonu

V předchozí lekci, Dekorátory podruhé - Parametrické a třídní dekorátory, jsme dokončili téma s dekorátory.

V dnešním tutoriálu objektově orientovaného programování v Pythonu se podíváme na další prvky tříd, které ještě neznáme. Zaměříme se na vlastnosti (properties). Ukážeme si, jak díky nim elegantně vyřešíme nastavování a validaci hodnot atributů.

Vlastnosti v Pythonu

Vlastnosti nám nabízejí elegantní a flexibilní způsob, jak pracovat s atributy objektů. Ve své podstatě nám umožňují definovat metody, které vypadají a chovají se jako atributy. Díky tomu máme pod kontrolou, co se stane, když je atribut nastaven, čten, nebo dokonce mazán, a to vše s uchováním stejného rozhraní jako u obyčejných atributů. To je užitečné v kontextu zapouzdření dat a omezení přímého přístupu k datům objektu, přestože rozhodně nejde o absolutní řešení. V mnoha jiných jazycích je zapouzdření implementováno pomocí soukromých atributů a veřejných metod (getters a setters). My už tušíme, že Python má jako obvykle svůj vlastní specifický přístup. Nabízí nám vlastnosti, které tyto koncepty spojují do jednoho elegantního a čitelného řešení.

Dekorátor @property

Klíčovým prvkem pro vytvoření vlastnosti je dekorátor @property. Máme-li jednoduchý atribut, který chceme upravit tak, aby obsahoval validaci nebo dodatečnou logiku při čtení, právě tímto dekorátorem ho přeměníme na vlastnost.

Mějme například třídu Osoba s atributem vek. Chceme zajistit, že věk bude vždy kladné číslo. Proto tento atribut přeměníme na vlastnost a přidáme odpovídající validaci:

class Osoba:
    def __init__(self, vek):
        self._vek = vek          # soukromý atribut vek

    @property                    #
    def vek(self):
        return f"Věk osoby je {self._vek} let."

    @vek.setter
    def vek(self, hodnota):
        if hodnota < 0:
            print("Chyba! Věk nemůže být záporný")
        else:
            self._vek = hodnota

clovek = Osoba(10)
clovek.vek = -20
print(clovek.vek)

V tomto příkladu je _vek skrytý atribut, který skutečně uchovává hodnotu věku. Veřejná vlastnost vek (už bez podtržítka) pak umožňuje číst a nastavovat tento skrytý atribut, ale s možností přidání další logiky, jako je validace. Pokus o vložení nesprávné hodnoty do atributu způsobí vypsání chybové hlášky a změna se neprovede.

Atribut je stále přímo dostupný i zvenčí, pokud k němu přistoupíme zápisem clovek._vek = -20. Pravidla přístupu k atributům zůstávají stejná, jak jsme si je ukázali v lekci Zapouzdření atributů podrobně.

Kombinace @property a @nazev_atributu.setter je syntaktická povinnost, nikoli pouze konvence. Zatímco @property bez setteru použít lze, pokus použít setter bez @property způsobí chybu. Pokud použijeme pouze @property (říká se mu také getter), získáme takzvanou read-only vlastnost. To znamená, že hodnotu této vlastnosti lze získat (přečíst), ale nemůže být přímo nastavena.

Pomocí @property, tedy getteru, data čteme a pomocí @nazev_atributu.setter, tedy setteru, data nastavujeme.

Kdy použít vlastnosti namísto metod

Vlastnosti v Pythonu nám umožňují kombinovat výhody metod a přístupu k atributům. Nabízí se otázka, kdy je vhodné vlastnosti použít místo klasických metod. Podívejme se na několik situací, kdy jsou vlastnosti vhodnou volbou:

  • přirozený přístup k datům – pokud chceme, aby se přístup k některým datům třídy zdál být jako k obyčejnému atributu, ale potřebujeme více kontroly nad tím, co se děje při čtení nebo zápisu těchto dat,
  • zachování kompatibility – pokud máme existující třídu s veřejným atributem a chceme přidat nějakou logiku při čtení nebo zápisu tohoto atributu, změníme ho na vlastnost bez nutnosti měnit rozhraní třídy. To znamená, že stávající kód, který tuto třídu využívá, bude fungovat beze změn,
  • validace dat – vlastnosti jsou ideální pro implementaci validace dat.

Vzhledem k principům, na kterých je Python postavený, vlastnosti neposkytují z hlediska zapouzdření příliš robustní ochranu. Stále je naší odpovědností vyvarovat se přímým zásahům do soukromých nebo privátních atributů.

Podívejme se na dosavadní výklad formou komplexnějšího příkladu. Představme si, že máme třídu Regulator, která simuluje teplotní regulátor. Chceme, aby uživatel mohl nastavit požadovanou teplotu, ale zároveň chceme zajistit, že tato teplota nebude příliš vysoká ani příliš nízká (např. mezi 10 °C a 30 °C):

class Regulator:
    def __init__(self):
        self._teplota = 20  # výchozí teplota

    @property
    def teplota(self):
        return f"Aktuálně je teplota nastavena na {self._teplota}°C."

    @teplota.setter
    def teplota(self, nova_teplota):
        if 10 <= nova_teplota <= 30:
            self._teplota = nova_teplota
        else:
            print(f"Chyba! Teplota {nova_teplota}°C je mimo povolený rozsah (10°C - 30°C).")

    def nastav_teplotu_v_kelvinech(self, teplota_k):
        teplota_c = teplota_k - 273.15
        if 10 <= teplota_c <= 30:
            self._teplota = teplota_c
            return f"Aktuálně je teplota nastavena na {teplota_k}°K."
        else:
            return f"Chyba! Teplota {teplota_k}K je mimo povolený rozsah v °C (10°C - 30°C)."

# Použití:
regulator = Regulator()
print(regulator.teplota)  # Vypíše: Aktuálně je teplota nastavena na 20°C.

regulator.teplota = 25
print(regulator.teplota)

regulator.teplota = 35    # Vypíše chybu, teplota zůstává 25°C
print(regulator.teplota)

print(regulator.nastav_teplotu_v_kelvinech(298.15))

V tomto rozšířeném příkladu uživatel nastavuje teplotu v °C pomocí vlastnosti @teplota.setter, ale má také možnost nastavit teplotu v kelvinech pomocí metody nastav_teplotu_v_kelvinech(). V praxi to znamená, že pokud existuje nějaká specifická funkce nebo způsob manipulace s daty, které se často nepoužívají nebo jsou složitější, je lepší použít tradiční metodu.

Pokud však chceme, aby se některé často prováděné akce (např. validace) prováděly automaticky při čtení nebo nastavování hodnoty, jsou vlastnosti ideálním řešením.

Vlastnost deleter

Vlastností v Pythonu je možné nejen číst a nastavovat, ale i mazat. Pro tento účel nám Python nabízí možnost definovat metodu, která se vyvolá, když s naší vlastností použijeme klíčové slovo del. Tuto metodu označujeme pomocí dekorátoru @<název_vlastnosti>.deleter.

Vytvoření vlastnosti deleter

Představme si následující situaci. Máme třídu, která reprezentuje dokument. Po smazání vlastnosti obsah chceme vymazat také hodnotu atributu _obsah, který zastupovala, a nastavit vlastnost smazano na True:

class Dokument:
    def __init__(self, obsah):
        self._obsah = obsah
        self.smazano = False

    @property
    def obsah(self):
        if not self.smazano:
            return self._obsah
        return "Dokument byl smazán."

    @obsah.deleter
    def obsah(self):
        self._obsah = None
        self.smazano = True
        print("Dokument byl smazán.")

# Použití:
doc = Dokument("Toto je obsah mého dokumentu.")
print(doc.obsah)  # Vypíše: Toto je obsah mého dokumentu.

del doc.obsah     # Vypíše: Dokument byl smazán.
print(doc.smazano)  # Vypíše: True

Zatímco setter je užitečný pro validaci hodnot nebo pro spuštění určité logiky po nastavení hodnoty, deleter použijeme, pokud chceme:

  • uvolnit prostředky – pokud naše třída spravuje prostředky, jako jsou soubory nebo síťová spojení, deleter využijeme pro jejich uvolnění,
  • aktualizovat související atributy – stejně jako v našem příkladu s dokumentem může být nutné aktualizovat některé související atributy.

Deleter je v praxi mnohem méně běžný než gettery a settery. Mnoho programátorů v Pythonu jej nikdy nepoužije. Je to proto, že v mnoha případech není třeba explicitně ručit za mazání atributů nebo jejich prostředků. Pokud však máme konkrétní důvod pro jeho použití, je dobré vědět, že tuto možnost máme k dispozici.

V následující lekci, Vlastnosti v Pythonu podruhé - Pokročilé vlastnosti a dědění, se v práci s vlastnostmi zaměříme na dědění, časté chyby a vytváření vlastních dekorátorů pro vlastnosti.


 

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