18. diel - Vlastnosti v Pythone
V predchádzajúcej lekcii, Dekorátory druhýkrát - Parametrické a triedne dekorátory , sme dokončili tému s dekorátormi.
V dnešnom tutoriáli objektovo orientovaného programovania v Pythone sa pozrieme na ďalšie prvky tried, ktoré ešte nepoznáme. Zameriame sa na vlastnosti (properties). Ukážeme si, ako vďaka nim elegantne vyriešime nastavovanie a validáciu hodnôt atribútov.
Vlastnosti v Pythone
Vlastnosti nám ponúkajú elegantný a flexibilný spôsob, ako pracovať s atribútmi objektov. Vo svojej podstate nám umožňujú definovať metódy, ktoré vyzerajú a chovajú sa ako atribúty. Vďaka tomu máme pod kontrolou, čo sa stane, keď je atribút nastavený, čítaný, alebo dokonca mazaný, a to všetko s uchovaním rovnakého rozhrania ako pri obyčajných atribútoch. To je užitočné v kontexte zapuzdrenia dát a obmedzenia priameho prístupu k dátam objektu, hoci rozhodne nejde o absolútne riešenie. V mnohých iných jazykoch je zapuzdrenie implementované pomocou súkromných atribútov a verejných metód (getters a setters). My už tušíme, že Python má ako obvykle svoj vlastný špecifický prístup. Ponúka nám vlastnosti, ktoré tieto koncepty spájajú do jedného elegantného a čitateľného riešenia.
Dekorátor @property
Kľúčovým prvkom pre vytvorenie vlastnosti je dekorátor
@property
. Ak máme jednoduchý atribút, ktorý chceme upraviť
tak, aby obsahoval validáciu alebo dodatočnú logiku pri čítaní, práve
týmto dekorátorom ho premeníme na vlastnosť.
Majme napríklad triedu Person
s atribútom age
.
Chceme zaistiť, že vek bude vždy kladné číslo. Preto tento atribút
premeníme na vlastnosť a pridáme zodpovedajúcu validáciu:
class Person:
def __init__(self, age):
self._age = age # private attribute age
@property
def age(self):
return f"The age of the person is {self._age} years."
@age.setter
def age(self, value):
if value < 0:
print("Error! Age cannot be negative.")
else:
self._age = value
person = Person(10)
person.age = -20
print(person.age)
V tomto príklade je _age
skrytý atribút, ktorý skutočne
uchováva hodnotu veku. Verejná vlastnosť age
(už bez
podčiarkovníka) potom umožňuje čítať a nastavovať tento skrytý
atribút, ale s možnosťou pridania ďalšej logiky, ako je validácia. Pokus o
vloženie nesprávnej hodnoty do atribútu spôsobí vypísanie chybovej
hlášky a zmena sa nevykoná.
Atribút je stále priamo dostupný aj zvonku, pokiaľ k nemu
pristúpime zápisom person._age = -20
. Pravidlá prístupu k
atribútom zostávajú rovnaké, ako sme si ich ukázali v lekcii Zapúzdrenie
atribútov podrobne.
Kombinácia @property
a @attribute_name.setter
je
syntaktická povinnosť, nie iba konvencia. Zatiaľ čo @property
bez setteru použiť je možné, pokus použiť setter bez
@property
spôsobí chybu. Pokiaľ použijeme iba
@property
(hovorí sa mu tiež getter), získame takzvanú
read-only vlastnosť. To znamená, že hodnotu tejto vlastnosti
možno získať (prečítať), ale nemôže byť priamo
nastavená.
Pomocou @property
, teda getteru, dáta čítame a
pomocou @attribute_name.setter
, teda setteru, dáta
nastavujeme.
Kedy použiť vlastnosti namiesto metód
Vlastnosti v Pythone nám umožňujú kombinovať výhody metód a prístupu k atribútom. Ponúka sa otázka, kedy je vhodné vlastnosti použiť namiesto klasických metód. Pozrime sa na niekoľko situácií, kedy sú vlastnosti vhodnou voľbou:
- prirodzený prístup k dátam – ak chceme, aby sa prístup k niektorým dátam triedy zdal byť ako k obyčajnému atribútu, ale potrebujeme viac kontroly nad tým, čo sa deje pri čítaní alebo zápise týchto dát,
- zachovanie kompatibility – ak máme existujúcu triedu s verejným atribútom a chceme pridať nejakú logiku pri čítaní alebo zápise tohto atribútu, zmeníme ho na vlastnosť bez nutnosti meniť rozhranie triedy. To znamená, že existujúci kód, ktorý túto triedu využíva, bude fungovať bez zmien,
- validácia dát – vlastnosti sú ideálne na implementáciu validácie dát.
Vzhľadom na princípy, na ktorých je Python postavený, vlastnosti neposkytujú z hľadiska zapuzdrenia príliš robustnú ochranu. Stále je našou zodpovednosťou vyvarovať sa priamym zásahom do súkromných alebo privátnych atribútov.
Pozrime sa na doterajší výklad formou komplexnejšieho príkladu.
Predstavme si, že máme triedu Regulator
, ktorá simuluje
teplotný regulátor. Chceme, aby užívateľ mohol nastaviť požadovanú
teplotu, ale zároveň chceme zaistiť, že táto teplota nebude príliš
vysoká ani príliš nízka (napr. medzi 10 °C a 30 °C):
class Regulator:
def __init__(self):
self._temperature = 20 # default temperature
@property
def temperature(self):
return f"The current temperature is set to {self._temperature} °C."
@temperature.setter
def temperature(self, new_temperature):
if 10 <= new_temperature <= 30:
self._temperature = new_temperature
else:
print(f"Error! The temperature {new_temperature} °C is outside the allowed range (10 °C - 30 °C).")
def set_temperature_in_kelvin(self, temperature_k):
temperature_c = temperature_k - 273.15
if 10 <= temperature_c <= 30:
self._temperature = temperature_c
return f"The current temperature is set to {temperature_k} °K."
else:
return f"Error! The temperature {temperature_k} K is outside the allowed range in °C (10 °C - 30 °C)."
# Usage:
regulator = Regulator()
print(regulator.temperature) # Displays: The current temperature is set to 20 °C.
regulator.temperature = 25
print(regulator.temperature)
regulator.temperature = 35 # Displays error, temperature remains at 25 °C
print(regulator.temperature)
print(regulator.set_temperature_in_kelvin(298.15))
V tomto rozšírenom príklade používateľ nastavuje teplotu v °C pomocou
vlastnosti @temperature.setter
, ale má tiež možnosť nastaviť
teplotu v kelvinoch pomocou metódy set_temperature_in_kelvin()
. V
praxi to znamená , že ak existuje nejaká špecifická
funkcia alebo spôsob manipulácie s dátami, ktoré
sa často nepoužívajú alebo sú
zložitejšie, je lepšie použiť tradičnú
metódu.
Pokiaľ však chceme, aby sa niektoré často vykonávané akcie (napr. validácia) vykonávali automaticky pri čítaní alebo nastavovaní hodnoty, sú vlastnosti ideálnym riešením.
Vlastnosť deleter
Vlastností v Pythone je možné nielen čítať a nastavovať, ale aj
mazať. Na tento účel nám Python ponúka možnosť
definovať metódu, ktorá sa vyvolá, keď s našou vlastnosťou použijeme
kľúčové slovo del
. Túto metódu označujeme pomocou
dekorátora @<property_name>.deleter
.
Vytvorenie vlastnosti
deleter
Predstavme si nasledujúcu situáciu. Máme triedu, ktorá reprezentuje
dokument. Po zmazaní vlastnosti content
chceme vymazať aj hodnotu
atribútu _content
, ktorý zastupovala, a nastaviť vlastnosť
deleted
na True
:
class Document:
def __init__(self, content):
self._content = content
self.deleted = False
@property
def content(self):
if not self.deleted:
return self._content
return "The document has been deleted."
@content.deleter
def content(self):
self._content = None
self.deleted = True
print("The document has been deleted.")
# Usage:
doc = Document("This is the content of my document.")
print(doc.content) # Displays: This is the content of my document.
del doc.content # Displays: The document has been deleted.
print(doc.deleted) # Displays: True
Zatiaľ čo setter je užitočný pre validáciu hodnôt alebo pre spustenie určitej logiky po nastavení hodnoty, deleter použijeme, ak chceme:
- uvoľniť prostriedky – ak naša trieda spravuje prostriedky, ako sú súbory alebo sieťové spojenia, deleter využijeme na ich uvoľnenie,
- aktualizovať súvisiace atribúty – rovnako ako v našom príklade s dokumentom môže byť potrebné aktualizovať niektoré súvisiace atribúty.
Deleter je v praxi oveľa menej bežný ako gettery a settery. Mnoho programátorov v Pythone ho nikdy nepoužije. Je to preto, že v mnohých prípadoch nie je potrebné explicitne ručiť za mazanie atribútov alebo ich prostriedkov. Ak však máme konkrétny dôvod na jeho použitie, je dobré vedieť, že túto možnosť máme k dispozícii.
V nasledujúcej lekcii, Vlastnosti v Pythone - Pokročilé vlastnosti a dedenie, sa v práci s vlastnosťami zameriame na dedenie, časté chyby a vytváranie vlastných dekorátorov pre vlastnosti.