IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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 5 - Zapouzdření atributů podrobně v Pythonu

V minulé lekci, Hrací kostka v Pythonu podruhé - Překrývání metod a random, jsme se naučili překrývat metody, používat vnitřní import a dokončili jsme hrací kostku.

V tomto tutoriálu objektově orientovaného programování v Pythonu se podrobněji zaměříme na práci s privátními atributy a jejich zapouzdřením. Ukážeme si jak vytvářet atributy za běhu i jak tomu lze v kódu našich tříd zabránit.

Zapouzdření v Pythonu detailně

Nyní už máme dostatek znalostí, abychom se důkladně zahloubali nad možnostmi zapouzdření. Řekli jsme si, že i když nám Python umožňuje nastavovat atributy privátně, nejsou od chtivých rukou programátora zcela oddělené.

Uveďme si příklad s naší kostkou. Upravíme atribut pocet_sten na skutečně privátní pomocí dvou podtržítek a zkusme k němu přistoupit v kódu, změnit jeho hodnotu a tu vypsat:

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 konzoli 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 velmi zajímavé chování. Zdá se, že Python nám umožňuje bez vyvolání chyby změnit privátní atribut, ale na tuto změnu pak nebere ohled. Bohužel, je to poněkud komplikovanější.

Už jsme si řekli, že v Pythonu neexistuje striktní privátnost, jak ji známe z jiných jazyků (jako je Java nebo C++). Místo toho Python používá konvenci zvanou "name mangling". Když deklarujeme atribut s dvojitým podtržítkem, např. __pocet_sten, Python tento název automaticky změní na _NazevTridy__pocet_sten. V případě našeho kódu se __pocet_sten interně stává atributem s názvem _Kostka__pocet_sten.

Náš pokus o přiřazení nové hodnoty tomuto atributu (kostka.__pocet_sten = 365) tak vůbec neupravil původní privátní atribut __pocet_sten třídy Kostka. Vytvořil totiž atribut zcela nový. Původní privátní atribut, který se díky dvojitému podtržítku přejmenoval na _Kostka__pocet_sten, zůstává nezměněný. Proto poslední výpis v našem pokusu potvrdil, že kostka je stále desetistěnná.

Přístup k privátnímu atributu

Pokud bychom opravdu chtěli počet stěn kostky změnit, museli bychom použít tento kód:

desetistenna._Kostka__pocet_sten = 365
print(desetistenna)

V konzoli pak uvidíme:

Změna privátního atributu:
Kostka s 365 stěnami.

Proč jde o špatnou praktiku

Hlavním důvodem, proč jsou některé atributy označeny jako privátní, je zapouzdření. Zapouzdření je jedním z klíčových principů OOP. Přímý přístup k privátním atributům tuto koncepci narušuje. Představme si, že se vývojář třídy později rozhodne změnit implementaci nebo strukturu interního atributu a my mu v jiné části kódu "natvrdo" měníme jeho hodnotu. Máloco je lepší způsob, jak do kódu zanést neočekávané chyby. Přístup k privátním atributům také výrazně zhorší čitelnost kódu.

I když můžeme technicky přistupovat k privátním atributům přes "name mangling", měli bychom to dělat jen v opravdu výjimečných situacích, a raději vůbec ne. Téměř vždy je lepší respektovat zapouzdření a pracovat s objekty tak, jak byly navrženy.

Vytváření atributů za běhu

Řekli jsme si, že pokus o přímé přepsání privátního atributu skončil tak, že Python místo toho vytvořil atribut nový. V Pythonu je povolena velká míra dynamiky, a to zahrnuje schopnost přidávat atributy k objektům za běhu. Platí to i pro metody, ale tam už jde o náročnější postup, který zatím výrazně přesahuje naše znalosti a zde se jím nebudeme zabývat. Tato schopnost je jedním z rysů, které dělají Python obzvláště flexibilním a mocným, ale současně je skvělým kandidátem na to být zdrojem potenciálních chyb.

Třídy v Pythonu jsou tedy modifikovatelné za běhu. Když vytvoříme instanci třídy, Python nám umožňuje této instanci přidat nové atributy (a metody), i když nebyly definovány v původní třídě.

Ukažme si příklad pro atribut:

class Zvire:
    pass

pes = Zvire()
pes.rasa = "Jsem nový jezevčík přidaný za běhu!"
print(pes.rasa)

Ve výstupu konzole uvidíme:

Přidání atributu za běhu:
Jsem nový jezevčík přidaný za běhu!

Je zřejmé, že tato vlastnost Pythonu vede do nebezpečných vod :-) Naprogramujeme si dokonalou třídu, jen aby nám do ní kolega vložil své vlastní atributy, které nemusí být úplně konzistentní s naším návrhem. Potenciální moře problémů je jasné. Naštěstí pro to má Python řešení.

Využití atributu __slots__

V Pythonu je __slots__ speciální atribut třídy, který definuje pevný seznam atributů, které instance této třídy může mít. Použitím __slots__ tedy vyloučíme možnost vytvářet nové atributy mimo definovaný seznam:

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__

Pokud se pokusíme přidat atribut rok_vyroby, který není v definici __slots__, v konzoli uvidíme chybu:

Přidání atributu mimo __slots__:
AttributeError: 'Auto' object has no attribute 'rok_vyroby'

To je pro tuto lekci vše :-)

V následujícím kvízu, Kvíz - Konstruktory, zapouzdření a překrývání v Pythonu, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.


 

Předchozí článek
Hrací kostka v Pythonu podruhé - Překrývání metod a random
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Kvíz - Konstruktory, zapouzdření a překrývání v Pythonu
Článek pro vás napsal Karel Zaoral
Avatar
Uživatelské hodnocení:
162 hlasů
Karel Zaoral
Aktivity