Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

5. diel - Zapuzdrenie atribútov podrobne v Pythone

V minulej lekcii, Hracia kocka v Pythone - 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áli 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 sides_count 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 RollingDie:

    def __init__(self,sides_count=6):
        self.__sides_count = sides_count

    def get_sides_count(self):
        """
        Returns the number of sides the die has.
        """
        return self.__sides_count

    def __str__(self):
        """
        Returns a textual representation of our die.
        """
        return str(f"A rolling die with {self.__sides_count} sides.")

tenSided = RollingDie(10)

print(f"Before attempting to modify the private attribute: {tenSided}")
tenSided.__sides_count = 365
print(f"We modified the attribute to the value: {tenSided.__sides_count}")
print(f"After attempting to modify the private attribute: {tenSided}")

V konzole uvidíme výstup:

Attempt to modify a private attribute from outside:
Before attempting to modify the private attribute: A rolling die with 10 sides.
We modified the attribute to the value: 365
After attempting to modify the private attribute: A rolling die with 10 sides.

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. __sides_count, Python tento názov automaticky zmení na _ClassName__sides_count. V prípade nášho kódu sa __sides_count interne stáva atribútom s názvom _RollingDie__sides_count.

Náš pokus o priradenie novej hodnoty tomuto atribútu (die.__sides_count = 365) tak vôbec neupravil pôvodný privátny atribút __sides_count triedy RollingDie. Vytvoril totiž atribút úplne nový. Pôvodný privátny atribút, ktorý sa vďaka dvojitému podtržítku premenoval na _RollingDie__sides_count, 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:

tenSided._RollingDie__sides_count = 365
print(tenSided)

V konzole potom uvidíme:

Modification of private attribute:
A rolling die with 365 sides.

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 Animal:
    pass

dog = Animal()
dog.breed = "I am a new dachshund added at runtime!"
print(dog.breed)

Vo výstupe konzoly uvidíme:

Adding attribute at runtime:
I am a new dachshund added at runtime!

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 Car:
    __slots__ = ['brand', 'model']

    def __init__(self, brand="Skoda", model="Superb"):
        self.brand = brand
        self.model = model

car = Car()

print(car.brand)   # Skoda
print(car.model)   # Superb

car.year_of_manufacture = "2023"     # This will raise an error because 'year_of_manufacture' is not in __slots__

Pokiaľ sa pokúsime pridať atribút year_of_manufacture, ktorý nie je v definícii __slots__, v konzole uvidíme chybu:

Adding an attribute outside of __slots__:
AttributeError: 'Car' object has no attribute 'year_of_manufacture'

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í.


 

Predchádzajúci článok
Hracia kocka v Pythone - Prekrývanie metód a random
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Kvíz - Konštruktory, zapuzdrenie a prekrývanie v Pythone
Článok pre vás napísal Karel Zaoral
Avatar
Užívateľské hodnotenie:
1 hlasov
Karel Zaoral
Aktivity