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.