2.11 Vlastná trieda a knižnica v MicroPython
Objekty a triedy v MicroPython
Vytváranie vlastných tried v jazyku MicroPython je rovnaké ako v Pythone, sú tu výraznejšie rozdiely oproti C++:
- Triedu deklarujeme kľúčovým slovom class, rovnako ako v C++, no bez zložených zátvoriek.
- Každej metóde (objektovej funkcii) je ako prvý parameter odoslaná inštancia objektu, zvykneme ju nazvať self, no v podstate ju môžeme nazvať ľubovoľne (v C++ sa nazýva this a nie je uvedená v parametroch).
- Konštruktor je funkcia s názvom __init__().
- Premenné inštancie je potrebné deklarovať v konštruktore (cez self) - detaily sú v dokumentácii Python.
class Osoba:
def __init__(self, meno, vek = None):
# údaje konštruktora uložíme do premenných inštancie
self.meno = meno
self.vek = vek
def Predstavenie(self):
vek = f" a mám {self.vek} rokov" if self.vek else ""
return f"Ahoj, volám sa {self.meno}{vek}."
osoby = (
Osoba("Jožo", 17),
Osoba("Anča", 16),
Osoba("Noel")
)
sucet = pocet = 0
for o in osoby:
print(o.Predstavenie())
if o.vek:
sucet += o.vek
pocet += 1
print(f"Priemerný vek: {sucet/pocet:.3g}")
Hoci Python nevyžaduje nazývať „sám seba“ menom self, je to určitá konvencia a mali by sme ju rešpektovať.
Ako zistiť, čo všetko objekt obsahuje?
Niekedy pri experimentovaní nevieme, aké funkcie alebo premenné má objekt (napríklad z cudzej knižnice) k dispozícii. Môžeme využiť pomôcku
dir(objekt), ktorá vypíše zoznam všetkého - premenných aj funkcií, ktoré môžeme použiť.
Zdieľané premenné triedy
- Premenné deklarované priamo v triede sú zdieľané - voláme ich premenné triedy.
- V hlavnom programe k nim môžeme pristupovať aj cez názov triedy, čo je zrozumiteľnejšie.
- V metóde k nim pristupujeme cez
type(self).
class Osoba:
# zdieľané premenné triedy
pozdrav = "Ahoj"
pocet = 0
def __init__(self, meno, vek = None):
self.meno = meno
self.vek = vek
# zväčšíme zdieľaný počet
type(self).pocet += 1
def Predstavenie(self):
vek = f" a mám {self.vek} rokov" if self.vek else ""
return f"{self.pozdrav}, volám sa {self.meno}{vek}."
osoby = (Osoba("Jožo", 17), Osoba("Anča", 16), Osoba("Noel"))
Osoba.pozdrav = "Nazdar"
print("Počet osôb:", Osoba.pocet)
for o in osoby:
print(o.Predstavenie())
Magické metódy
Objektu môžeme definovať takzvané „magické“ metódy, ktoré umožňujú vykonať špecifické transformácie, či preťažiť operátory:
- Pokiaľ chceme mať možnosť priamo vypísať inštanciu objektu príkazom
print(inštancia), môžeme definovať funkciu__str__(), ktorá transformuje objekt na text v požadovanej forme. - Tiež je užitočnou funkcia
__call__(), ktorá umožňuje volať objekt samotný v podobe funkcie. Môže to byť skratka pre vrátenie hodnoty (napríklad namiestotlacidlo.value()stačí zapísaťtlacidlo()) alebo skratka na nastavenie hodnoty (napríklad namiestoled.value(1)stačí zapísaťled(1)).
class Osoba:
def __init__(self, meno, vek = None):
self.meno = meno
self.vek = vek
# čo sa má vypísať pri konverzii na text a pri print(inštancia)
def __str__(self):
vek = f" ({self.vek})" if self.vek else ""
return f"{self.meno}{vek}"
# čo sa má stať pri zavolaní inštancia(parametre)
def __call__(self, meno, vek = None):
print(f"Mením osobu {self}", end = "")
self.meno = meno
self.vek = vek
print(f" na {self}.")
osoby = (Osoba("Jožo", 17), Osoba("Anča", 16), Osoba("Noel"))
print("Osoby:")
for o in osoby:
print(f" - {o}")
osoby[2]("Fero", "20")
print("\nNový zoznam osôb:")
for c, o in enumerate(osoby, 1):
print(f" {c}. {o}")
Magické metódy môžeme použiť aj pre porovnávacie a matematické operácie - okrem parametra self musia mať ešte druhý parameter, s ktorým majú operovať:
| == | __eg__() |
| != | __ne__() |
| < | __lt__() |
| > | __gt__() |
| <= | __le__() |
| >= | __gr__() |
Matematické operátory musia na konci vrátiť nový objekt:
| + | __add__() |
| - | __sub__() |
| * | __mul__() |
| / | __truediv__() |
| // | __floordiv__() |
Môžeme využiť aj operátory priradenia, no ich funkcie musia na konci vrátiť self, aby sa zmenený objekt uložil:
| += | __iadd__() |
| -= | __isub__() |
| *= | __imul__() |
| /= | __itruediv__() |
| //= | __ifloordiv__() |
class Osoba:
def __init__(self, meno, vek = None):
self.meno = meno
self.vek = vek
self.peniaze = 0
def __str__(self):
vek = f" ({self.vek})" if self.vek else ""
return f"{self.meno}{vek}, {self.peniaze} €"
def __iadd__(self, suma):
self.peniaze += suma
return self
osoby = [Osoba("Jožo", 17), Osoba("Anča", 16), Osoba("Noel")]
osoby[1] += 20
osoby[2] += 15
print("Osoby:")
for o in osoby:
print(f" - {o}")
Vnútorné premenné a kontrolovaný prístup cez dekorátory
V Pythone neexistuje privátna časť triedy. Ak chceme dať najavo, že niektorá premenná alebo funkcia nemá byť volaná (teda chceme ju chápať ako „privátnu“), jej názov začneme symbolom „_“, napríklad _pocet.
V novších verziách jazyka Python je možné využiť „__“, potom premenná / funkcia skutočne nie je dostupná zvonku. Táto možnosť však v MicroPythone chýba.
V knižniciach (ovládačoch) ku senzorom sa často stretneme s objektom, z ktorého zisťujeme hodnotu senzora prečítaním hodnoty z objektovej premennej. Teda aspoň sa to tak navonok javí (keďže za názvom premennej nepíšeme žiadne zátvorky), no v skutočnosti nejde o premennú, ale o akúsi „zamaskovanú funkciu“ - nazývame ju „getter“. Tá môže spustiť proces merania cez senzor, či v jednoduchšej situácii proste len vrátiť hodnotu internej premennej.
Teda namiesto volania funkcie senzor.DajTeplotu() použijeme len senzor.teplota. Či v našom príklade s osobami namiesto osoba.DajVek() použijeme osoba.vek. Takúto funkciu zadefinujeme zapísaním dekorátora @property do riadku pred definíciu property funkcie P.
A čo keď chceme takúto vnútornú premennú nastaviť? Môžeme samozrejme použiť klasickú funkciu osoba.NastavVek(v) alebo k property pridať jej „setter“ a zapísať to navonok priamym priradením do premennej: osoba.vek = v. Pri nastavovaní samozrejme môžeme overiť, či nastavovaná hodnota spĺňa stanovené požiadavky (typicky rozsah hodnôt). Takúto funkciu zadefinujeme zapísaním dekorátora @P.setter - kde P je názov property funkcie (getter).
Situáciu najlepšie ilustruje jednoduchá ukážka:
class Osoba:
def __init__(self, meno, vek = None):
self.meno = meno
# „súkromná“ premenná, uchováva vek, na úvod nastavíme na None
self._vek = None
# na overované nastavenie použijeme nižšie definovanú funkciu (setter)
self.vek = vek
# definujeme „getter“ - funkciu pre odovzdanie internej hodnoty
@property
def vek(self):
return self._vek
# definujeme „setter“ - funkciu pre nastavenie internej hodnoty
@vek.setter
def vek(self, novy_vek):
if 6 <= novy_vek <= 20:
self._vek = novy_vek
else:
print("CHYBA: osobe nie je možné nastaviť vek", novy_vek)
# ponechá pôvodný vek
osoba = Osoba("Anča", 5) # vek sa nepodarí nastaviť
print("nastavený vek:", osoba.vek)
osoba.vek = 25 # vek sa nepodarí nastaviť
print("nastavený vek:", osoba.vek)
osoba.vek = 15 # tentokrát sa podarí
print(osoba.meno, "má", osoba.vek, "rokov.")
Dedenie
Pri deklarácii potomka dávame predka zo zátvorky, teda class Potomok(Predok). Ak potomok potrebuje v konštruktore zavolať konštruktor predka, použijeme výraz super().__init__().
class Osoba:
pozdrav = "Ahoj"
pocet = 0
def __init__(self, meno, vek = None):
self.meno = meno
self.vek = vek
type(self).pocet += 1
def __str__(self):
vek = f" ({self.vek})" if self.vek else ""
return f"{self.meno}{vek}"
def Predstavenie(self):
vek = f" a mám {self.vek} rokov" if self.vek else ""
return f"{self.pozdrav}, volám sa {self.meno}{vek}."
class Zamestnanec(Osoba):
platy = pocet = 0
def __init__(self, meno, plat):
self.plat = plat
type(self).platy += plat
# zavoláme aj konštruktor triedy Osoba
super().__init__(meno)
osoby = (Osoba("Jožo", 17), Osoba("Anča", 16), Osoba("Noel"))
zamestnanci = (Zamestnanec("Eva", 1000), Zamestnanec("Jano", 800))
Zamestnanec.pozdrav = "Práci česť"
for o in osoby + zamestnanci:
print(o.Predstavenie())
print(f"\nPočet osôb: {Osoba.pocet}")
print(f"Počet zamestnancov: {Zamestnanec.pocet}")
print(f"Priemerný plat: {Zamestnanec.platy / Zamestnanec.pocet :.0f} €")
V uvedenom príklade si všimnite, že počet osôb určuje len počet osôb, nie zamestnancov. Ak by sme chceli do osôb zarátať aj zamestnancov, potom by sme v riadku 8 uviedli Osoba.pocet += 1 a za riadkom 22 by sme počítali zvlášť aj zamestnancov cez type(self).pocet += 1 alebo Zamestnanec.pocet += 1.
Knižnice objektov
Vlastnú triedu zvykneme uložiť do súboru s rovnakým menom, ale malými písmenami, teda napríklad trieda Teplomer bude v súbore teplomer.py. To samozrejme nebude platiť v prípade, že chceme mať v jednom súbore viac tried. V oboch prípadoch takýto súbor nazývame knižnica alebo tiež modul.
Z vlastnej knižnice importujeme triedu rovnakým spôsobom, ako zo štandardných modulov, napríklad from teplomer import Teplomer.