12. diel - Statika v Pythone - Triedne atribúty
V predchádzajúcom cvičení, Riešené úlohy k 8.-11. lekciu OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V nasledujúcom tutoriáli objektovo orientovaného programovania v Pythone sa budeme venovať pojmu statika. Až doteraz sme boli zvyknutí, že dáta (stav) nesie inštancie. Premenné (atribúty), ktoré sme definovali, teda patrili inštancii a boli pre každú inštanciu jedinečné. OOP však umožňuje definovať atribúty a metódy na samotnej triede. Týmto prvkom hovoríme statické (alebo tiež triedne) a sú nezávislé na inštancii. Najprv sa zameriame na triedne atribúty.
POZOR! Dnešná lekcia vám ukáže statiku, teda postupy, ktoré v podstate narúšajú objektový model. OOP ich obsahuje len pre špeciálne prípady a všeobecne platí, že všetko ide napísať bez statiky. Vždy musíme starostlivo zvážiť, či statiku naozaj nutne potrebujeme. Všeobecné odporúčanie je statiku vôbec nepoužívať, pokiaľ si nie sme úplne istí, čo robíme. Podobne ako globálne premenné je statika v objektovom programovaní niečo, čo umožňuje písať zlý kód a porušovať dobré praktiky. Dnes si ju teda skôr vysvetlíme. Znalosti použite s rozvahou, na svete bude potom menej zla.
Triedne atribúty
Ako triedne môžeme označiť rôzne prvky. Začnime pri atribútoch. Ako
sme už v úvode spomenuli, statické prvky patria triede, nie inštancii. Dáta
v nich uložené teda môžeme čítať bez ohľadu na to, či nejaká
inštancia existuje. V podstate môžeme povedať, že triedne atribúty sú
zdieľané medzi všetkými inštanciami triedy. Sú definované vo vnútri
triedy, ale mimo akúkoľvek metódu a existujú aj pred vytvorením akejkoľvek
inštancie triedy. Vysvetlíme si to na príklade. Predstavme si, že
potrebujeme zdieľať nejaký údaj medzi všetkými inštanciami triedy alebo
chceme, aby bol tento údaj dostupný, aj keď ešte neexistuje žiadna
inštancia triedy. Založme si nový súbor (názov napr.
static.py
) a urobme si jednoduchú triedu User
:
class User: def __init__(self, name, password): self._name = name self._password = password self._logged_in = False def log_in(self, entered_name, entered_password): if self._name == entered_name and self._password == entered_password: self._logged_in = True return True else: self._logged_in = False return False # name or password is wrong
Trieda je pomerne jednoduchá. Reprezentuje používateľov nejakého
systému. Každá inštancia používateľa má svoje meno, heslo a tiež sa o
nej vie, či je prihlásená alebo nie. Aby sa užívateľ prihlásil, zavolá
sa na ňom metóda log_in()
. Tá nesie v parametri meno a heslo,
ktoré človek za klávesnicou zadal. Metóda overí, či ide naozaj o tohto
používateľa a pokúsi sa ho prihlásiť. Vráti True/False
podľa toho, či prihlásenie prebehlo úspešne.
Ako do toho zapojíme triedny atribút? Keď sa nový užívateľ registruje,
systém mu napíše, akú minimálnu dĺžku musí jeho heslo mať. Toto číslo
ale musíme mať niekde uložené. Lenže vo chvíli, keď užívateľa
registrujeme, ešte nemáme jeho inštanciu k dispozícii. Objekt
skrátka nie je vytvorený a vytvorí sa až na základe dát získaných po
vyplnení formulára. Samozrejme by bolo veľmi prínosné, keby sme mali údaj
o minimálnej dĺžke hesla uložený v triede User
, pretože k
nemu logicky patrí. Ako to teda vyriešime? Údaj uložíme priamo v triede
User
do triedneho atribútu. Vytvoríme si k tomu
atribút minimal_password_length
:
class User: minimal_password_length = 6 def __init__(self, name, password): ... def log_in(self, entered_name, entered_password): ...
Až doteraz sme všetky dáta objektu pridávali až pri vzniku jeho inštancie pomocou konštruktora. Statika nám poskytuje riešenie, ako objekt vybaviť dátami ešte predtým, než vôbec vznikne akákoľvek jeho inštancia.
Poďme si atribút vypísať. K triednemu atribútu pristúpime priamo cez
triedu, syntax je ClassName.attribute_name
:
class User:
minimal_password_length = 6
def __init__(self, name, password):
self._name = name
self._password = password
print(User.minimal_password_length) # We notice the capital letter in the class name – this is indeed not an instance
Vidíme, že atribút skutočne patrí triede. Môžeme sa na neho pýtať v rôznych miestach programu bez toho, aby sme mali užívateľa vytvoreného. Ale pozor, na inštancii používateľa tento atribút nájdeme tiež:
class User:
minimal_password_length = 6
def __init__(self, name, password):
self._name = name
self._password = password
new_user = User("Thomas White", "passwordissword")
print(new_user.minimal_password_length) # the lowercase "n" in new_user indicates that we are indeed working with an instance
Vidíme teda, že triedne atribúty zdieľajú svoje hodnoty naprieč všetkými inštanciami danej triedy. Ale pozor! Pri zmene triedneho atribútu v inštancii zmeníme iba hodnotu pre danú inštanciu.
Pozrime sa na príklad:
class MyClass: class_attribute = 'This is class attribute data, available anytime without creating an instance.' instance = MyClass() print(instance.class_attribute) instance.class_attribute = 'Here we change the class attribute value in the instance.' print(instance.class_attribute) print(MyClass.class_attribute)
Vo výstupe konzoly uvidíme:
Class attribute:
This is class attribute data, available anytime without creating an instance.
Here we change the class attribute value in the instance.
This is class attribute data, available anytime without creating an instance.
Ako ďalšie praktické využitie triednych atribútov sa ponúka
číslovanie užívateľov. Budeme chcieť, aby mal každý používateľ
pridelené unikátne identifikačné číslo. Bez znalosti statiky by sme si
museli strážiť zvonku každé vytvorenie nového užívateľa a počítať
ich. My si však vytvoríme priamo na triede User
statický (=
triedny) atribút next_id
, kde bude vždy pripravené číslo pre
ďalšieho používateľa. Prvý používateľ bude mať id = 1
,
druhý 2
a tak ďalej. Užívateľovi teda pribudne nový atribút
id
, ktorý sa v konštruktore nastaví podľa hodnoty
next_id
. Poďme si to vyskúšať:
class User:
minimal_password_length = 6 # class attribute
next_id = 1 # class attribute
def __init__(self, name, password):
self._name = name
self._password = password
self._logged_in = False
self._id = User.next_id # assign the current id
User.next_id += 1 # prepare the id for the next instance
admin_user = User("Thomas Admin", "adminpassword")
regular_user = User("Thomas User", "userpassword")
print(f"User ID of {admin_user._name} is {admin_user._id}")
print(f"User ID of {regular_user._name} is {regular_user._id}")
Trieda si sama ukladá, aké bude id
jej inštancia. Toto
id
priradíme novej inštancii v konštruktore a zvýšime ho o
1
, aby bolo pripravené pre ďalšiu inštanciu.
Špecifiká dynamicky typovaného jazyka
Python je dynamicky typovaný jazyk. To znamená, že jeho možnosti sú oproti statickým jazykom (typicky C#) trochu širšie. Pozrime sa na konkrétne príklady.
Dynamické priradenie v Pythone
V Pythone vieme vytvoriť triedne atribúty za behu. Ide o rovnaký mechanizmus, aký sme si popísali v lekcii Zapúzdrenie atribútov podrobne v Pythone. Teda aj keď neexistovali pri definícii triedy. To, ako už vieme, je niečo, čo v jazykoch s pevnými definíciami tried, ako je C#, nie je možné. Pozrime sa na príklad:
class MyClass: pass # some program code # we realized we could use a class attribute, so let's create one: MyClass.new_class_attribute = "This is a new class attribute!"
Pretypovanie
Toto je síce zrejmé z povahy Pythona, ale rovnako sa o typovaní zmienime. Typ triedneho atribútu je možné ľahko zmeniť za behu:
class Person:
age = 30
print(f"Original age: {Person.age} (type {type(Person.age).__name__})")
# Now we change the type of the class attribute 'age' from a number to a string
Person.age = "Thirty years"
print(f"After type change: {Person.age} (type {type(Person.age).__name__})")
Funkcia type()
vracia triedu (alebo typ) objektu,
to už poznáme. Keď ale chceme získať iba meno triedy ako reťazec (v našom
príklade int
), použijeme syntax s magickým atribútom
.__name__
. O "mágii" v Pythone si povieme neskôr v kurze.
Pretypovanie je v niektorých prípadoch užitočné (napríklad keď
potrebujeme upraviť správanie objektu počas behu), ale môže tiež viesť k
chybám. Typicky pokiaľ nečakáme (zabudneme), že sa typ atribútu zmenil v
priebehu životného cyklu programu a pokúsime sa s ním pracovať ako s
pôvodným typom int
.
To je pre túto lekciu všetko.
V budúcej lekcii, Statika v Pythone druhýkrát - Statické a triedne metódy, dokončíme tému statiky. Preberieme statické a triedne metódy.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 0x (867 B)
Aplikácia je vrátane zdrojových kódov v jazyku Python