5. diel - Zapuzdrenie atribútov podrobne v Pythone
V minulej lekcii, Hracia kocka v Pythone druhýkrát - Prekrývanie metód a random , sme sa naučili prekrývať metódy, používať vnútorný import a dokončili sme hraciu kocku.
V tomto tutoriále objektovo orientovaného programovania v Pythone sa podrobnejšie zameriame na prácu s privátnymi atribútmi a ich zapuzdrením. Ukážeme si ako vytvárať atribúty za behu aj ako tomu možno v kóde našich tried zabrániť.
Zapuzdrenie v Pythone detailne
Teraz už máme dostatok znalostí, aby sme sa dôkladne zahĺbali nad možnosťami zapuzdrenia. Povedali sme si, že aj keď nám Python umožňuje nastavovať atribúty privátne, nie sú od chtivých rúk programátora úplne oddelené.
Uveďme si príklad s našou kockou. Upravíme atribút
pocet_sten
na skutočne privátne pomocou dvoch
podčiarkovníkov a skúsme k nemu pristúpiť v kóde, zmeniť jeho hodnotu a
tú vypísať:
class Kostka:
def __init__(self,pocet_sten=6):
self.__pocet_sten = pocet_sten
def vrat_pocet_sten(self):
"""
Vrátí počet stěn kostky.
"""
return self.__pocet_sten
def __str__(self):
"""
Vrací textovou reprezentaci kostky.
"""
return str(f"Kostka s {self.__pocet_sten} stěnami.")
desetistenna = Kostka(10)
print(f"Před pokusem o úpravu privátního atributu: {desetistenna}")
desetistenna.__pocet_sten = 365
print(f"Upravili jsme atribut na hodnotu: {desetistenna.__pocet_sten}")
print(f"Po pokusu o úpravu privátního atributu: {desetistenna}")
V konzole uvidíme výstup:
Pokus o změnu privátního atributu zvenčí:
Před pokusem o úpravu privátního atributu: Kostka s 10 stěnami.
Upravili jsme atribut na hodnotu: 365
Po pokusu o úpravu privátního atributu: Kostka s 10 stěnami.
To je veľmi zaujímavé správanie. Zdá sa, že Python nám umožňuje bez vyvolania chyby zmeniť privátny atribút, ale na túto zmenu potom neberie ohľad. Bohužiaľ, je to trochu komplikovanejšie.
Už sme si povedali, že v Pythone neexistuje striktná privátnosť, ako ju
poznáme z iných jazykov (ako je Java alebo C++). Namiesto toho Python používa konvenciu zvanú "name
mangling". Keď deklarujeme atribút s dvojitým podčiarkovníkom, napr.
__pocet_sten
, Python tento názov automaticky zmení na
_NazevTridy__pocet_sten
. V prípade nášho kódu sa
__pocet_sten
interne stáva atribútom s názvom
_Kostka__pocet_sten
.
Náš pokus o priradenie novej hodnoty tomuto atribútu
(kostka.__pocet_sten = 365
) tak vôbec neupravil pôvodný
privátny atribút __pocet_sten
triedy Kostka
.
Vytvoril totiž atribút úplne nový. Pôvodný privátny
atribút, ktorý sa vďaka dvojitému podtržítku premenoval na
_Kostka__pocet_sten
, zostáva nezmenený. Preto posledný výpis v
našom pokuse potvrdil, že kocka je stále desaťstenná.
Prístup k privátnemu atribútu
Pokiaľ by sme naozaj chceli počet stien kocky zmeniť, museli by sme použiť tento kód:
desetistenna._Kostka__pocet_sten = 365 print(desetistenna)
V konzole potom uvidíme:
Změna privátního atributu:
Kostka s 365 stěnami.
Prečo ide o zlú praktiku
Hlavným dôvodom, prečo sú niektoré atribúty označené ako privátne, je zapuzdrenie. Zapúzdrenie je jedným z kľúčových princípov OOP. Priamy prístup k privátnym atribútom túto koncepciu narúša. Predstavme si, že sa autor triedy neskôr rozhodne zmeniť implementáciu alebo štruktúru interného atribútu a my mu v inej časti kódu "natvrdo" meníme jeho hodnotu. Máločo je lepší spôsob, ako do kódu zaniesť neočakávané chyby. Prístup k privátnym atribútom tiež výrazne zhorší čitateľnosť kódu.
Aj keď môžeme technicky pristupovať k privátnym atribútom cez „name mangling“, mali by sme to robiť len v naozaj výnimočných situáciách, a radšej vôbec nie. Takmer vždy je lepšie rešpektovať zapuzdrenie a pracovať s objektmi tak, ako boli navrhnuté.
Vytváranie atribútov za behu
Povedali sme si, že pokus o priame prepísanie privátneho atribútu skončil tak, že Python namiesto toho vytvoril nový atribút. V Pythone je povolená veľká miera dynamiky, a to zahŕňa schopnosť pridávať atribúty k objektom za behu. Platí to aj pre metódy, ale tam už ide o náročnejší postup, ktorý zatiaľ výrazne presahuje naše znalosti a tu sa ním nebudeme zaoberať. Táto schopnosť je jedným z rysov, ktoré robia Python obzvlášť flexibilným a mocným, ale súčasne je skvelým kandidátom na to byť zdrojom potenciálnych chýb.
Triedy v Pythone sú teda modifikovateľné za behu. Keď vytvoríme inštanciu triedy, Python nám umožňuje tejto inštancii pridať nové atribúty (a metódy), aj keď neboli definované v pôvodnej triede.
Ukážme si príklad pre atribút:
class Zvire:
pass
pes = Zvire()
pes.rasa = "Jsem nový jezevčík přidaný za běhu!"
print(pes.rasa)
Vo výstupe konzoly uvidíme:
Přidání atributu za běhu:
Jsem nový jezevčík přidaný za běhu!
Je zrejmé, že táto vlastnosť Pythona vedie do nebezpečných vôd:-) Naprogramujeme si dokonalú triedu, len aby nám do nej kolega vložil svoje vlastné atribúty, ktoré nemusia byť úplne konzistentné s naším návrhom. Potenciálne more problémov je jasné. Našťastie pre to má Python riešenie.
Využitie atribútu
__slots__
V Pythone je __slots__
špeciálny atribút triedy, ktorý
definuje pevný zoznam atribútov, ktoré inštancia tejto triedy môže mať.
Použitím __slots__
teda vylúčime možnosť vytvárať
nové atribúty mimo definovaného zoznamu:
class Auto:
__slots__ = ['znacka', 'model']
def __init__(self, znacka="Skoda", model="Superb"):
self.znacka = znacka
self.model = model
auto = Auto()
print(auto.znacka) # Skoda
print(auto.model) # Superb
auto.rok_vyroby = "2023" # Tohle vyvolá chybu, protože 'rok_vyroby' není ve __slots__
Pokiaľ sa pokúsime pridať atribút rok_vyroby
, ktorý nie je
v definícii __slots__
, v konzole uvidíme chybu:
Přidání atributu mimo __slots__:
AttributeError: 'Auto' object has no attribute 'rok_vyroby'
To je pre túto lekciu všetko:-)
V nasledujúcom kvíze, Kvíz - Konštruktory, zapuzdrenie a prekrývanie v Pythone, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.