Skip to main content

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).
  • Pokiaľ chceme mať možnosť priamo vypísať inštanciu objektu príkazom print(inštancia), môžeme definovať špeciálnu funkciu __str__(), ktorá vráti text v požadovanej forme.
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

    # čo sa má vypísať pri print(inštancia)
    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}."

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 namiesto tlacidlo.value() stačí zapísať tlacidlo()) alebo skratka na nastavenie hodnoty (napríklad namiesto led.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"\nZoznamMením osobu {Osoba.pocet}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ť:

    Neexistujeporovnávacie operátory
    ==__eq__()
    !=__ne__()
    <__lt__()
    >__gt__()
    <=__le__()
    >=__ge__()

    Matematické operátory musia na konci vrátiť nový objekt:

    matematické operátory
    +__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:

    operátory priradenia
    +=__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 Ako@property zistiť,do riadku pred definíciu property funkcie P.

A čo všetkokeď objektchceme obsahuje?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).

NiekedySituáciu prinajlepšie experimentovaníilustruje nevieme,jednoduchá akéukážka:

funkcie
class aleboOsoba:
    premennédef __init__(self, objektmeno, 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 (napríkladsetter)
        zself.vek cudzej= knižnice)vek

    k# dispozícii.definujeme Môžeme využiť dve pomôcky:

  • vars(objekt) vypíše slovník s jeho aktuálne nastavenými premennými (údajmi);
  • dir(objekt) vypíše zoznam všetkého„getter“ - premennýchfunkciu ajpre funkcií,odovzdanie ktoréinternej môžemehodnoty použiť.
  • @property
def
vek(self): return self._vek # definujeme „setter“ - funkciu pre nastavenie internej hodnoty @vek.setter def vek(self, novy_vek): if novy_vek is None: return 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.