Linux E X P R E S

Facebook

Python 3 (10): importovanie, špeciálne funkcie objektov a dedenie

python.png

Touto časťou budeme pokračovať v tej predchádzajúcej – ostaneme pri objektovom programovaní. Pozrieme sa na to, ako v Pythone funguje dedenie, vrámci čoho si ukážeme, ako importovať vlastné moduly zatiaľ v podobe súborov. Okrem toho si spomenieme pár špeciálnych metód objektov.


Importovanie modulov

Doteraz sme o importovaní modulov hovorili len v druhej časti seriálu, kde sme importovali modul math pre sprístupnenie rôznych matematických funkcií, a pri manipulácií so súbormi a priečinkami pomocou funkcií modulu os. Pre importovanie sme použili príkaz import, za ktorým nasledoval názov modulu, ktorý sme importovali:

import math

Funkcie z modulu sme potom volali takto:

math.sqrt(47)

Pri importovaní máme ale viac možností, ako tak učiniť. Môžeme použiť komplexnejší príkaz from [názov modulu] import [názov funkcie]. Takto importujeme len konkrétny objekt (premenná, funkcia, trieda, …), ktorý je už ďalej vrámci programu dostupný samostatne:

Poznámka autora: Pre ukážku použijem funkciu dir() bez argumentu, ktorá nám vypíše všetky objekty definované v hlavnej triede – interpreteri.

$ python3 -q
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> from math import sqrt
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sqrt']
>>> sqrt(47)
6.855654600401044

Takouto formou môžeme importovať aj viacero objektov z jedného modulu tak, že jednotlivé názvy oddelíme čiarkami, prípadne pri použití hviezdičky (*) importujeme všetky objekty z daného modulu:

>>> from math import pi, e
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'e', 
'pi', 'sqrt']
>>> print(pi, e)
3.141592653589793 2.718281828459045
>>> sin
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'sin' is not defined
>>> from math import *
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 
'acos', 'acosh', 'asin', 'asinh', ...[ostatné objekty z modulu math]..., 'pi', 'pow', 
'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']
>>> sin
<built-in function sin>

Pri importovaní môžeme použiť ešte jedno kľúčové slovíčko – as. To nám umožní importovať modul alebo konkrétny objekt modulu pod vlastným názvom (do vlastnej premennej).

>>> from os import system as x
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'x']
>>> x("uname")
Linux
0

if __name__ == '__main__':

Python program môže byť samostatnou aplikáciou, ale zároveň aj modulom. Napríklad náš program z minulej časti, ktorý vytváral objekty (jednotlivé obdĺžniky), môžeme importovať ako modul, pokiaľ sa nachádza v rovnakom priečinku ako spustený program alebo interpreter. U mňa sa súbor s programom volá oop.py.

$ ls
oop.py
$ python3 -q
>>> import oop
Obdĺžnik s rozmermi 1x5 bol vytvorený.
Obdĺžnik s rozmermi 20x3 bol vytvorený.
Štvorec s rozmermi 42x42 bol vytvorený.
Obdĺžnik rect1
Rozmery: 1x5
Obsah: 5
Dĺžka uhlopriečky: 5.0990195135927845
Obdĺžnik rect2
Rozmery: 20x3
Obsah: 60
Dĺžka uhlopriečky: 20.223748416156685
Štvorec squar
Rozmery: 42x42
Obsah: 1764
Dĺžka uhlopriečky: 59.39696961966999

Vidíme však, že po importovaní do interpreteru sa celý importovaný modul spustil, a vždy sa tak stane bez ohľadu na to, akým spôsobom budeme modul alebo objekty modulu importovať. Je možné tomu zabrániť práve použitím podmienky if __name__ == '__main__'. Premenná __name__ obsahuje '__main__' vtedy, keď je program spúšťaný samostatne – je to pomenovanie pre hlavný program, čiže podmienka je splnená a jej obsah vykonaný.

Pri importovaní obsahuje táto premenná názov modulu, takže podmienka splnená nieje. Ideálne je použitie spolu s definovaním funkcie main(), ktorá síce v Pythone nemá žiaden kľúčový význam, oproti napríklad C jazykom, ale pre prehľadnosť je vhodné jej použitie. Jednoduchý príklad:

$ cat name_main.py
def main():
    print("!tevs ,johA"[::-1])

if __name__ == '__main__':
    main()
else:
    print("Importovaný", __name__)
$ python3 name_main.py
Ahoj, svet!
$ python3 -q
>>> import name_main
Importovaný name_main
>>> name_main.main()
Ahoj, svet!

A takto bude vyzerať program z predchádzajúcej časti seriálu po pridaní tejto podmienky a funkcie main(): odkaz na hastebin.



Špeciálne metódy objektov

Konečne sa dostávame naspäť k objektom, a to k ich špeciálnym metódam. Z minulej časti vieme, že jednou zo špeciálnych metód je konštruktor, čiže metóda __init__(), ktorá sa volá pri vytváraní objektu. Dnes si spomenieme len ďalšie dve špeciálne metódy. Metódu opačnú ku konštruktoru – deštruktor – __del__(), a metódu __str__(), ktorá sa volá pri konvertovaní objektu na reťazec (string).

Fungovanie oboch týchto metód v skratke vysvetlím a potom ukážem na príklade modulu s triedou, pomocou ktorej budeme vytvárať objekty – trojuholníky.

__del__()

Ako som už spomenul, deštruktor je špeciálna funkcia, ktorá je opozitom ku konštruktoru. Deštruktor je volaný vtedy, keď objekt zaniká, čiže keď je vymazaný pomocou funkcie del(objekt) alebo prepísaný. Aj keď to nieje až tak pravda a na použitie deštruktoru je potrebné dávať si pozor,  podrobnejšie informácie nájdete v oficiálnej dokumentácii. Ale v našom príklade a vo väčšine prípadov sa s problémom ohľadom tejto metódy nestretneme.

__str__()

Táto metóda predstavuje čitateľnú reprezentáciu objektu dátového typu string, resp. volá sa pri funkciách print()str.format(), pričom by mala vrátiť reťazec. Štandardne je táto metóda pri nových objektoch totožná s metódou __repr__(), ktorá takisto reprezentuje objekt v reťazci, ale narozdiel od __str__() by malo byť vždy jednoznačné, o aký objekt ide: <[trieda] object at 0x7f7b92946f60>.  V metóde __str__() potom môže byť reťazec, ktorý sa zobrazí užívateľovi.

>>> class Test:
...  def __str__(self): return "Objekt triedy Test"
...
>>> obj = Test()
>>> obj
<__main__.Test object at 0x7f7b9294e080>
>>> print(obj)
Objekt triedy Test

Prejdime už na náš hlavný príklad k tejto časti seriálu, a to na modul pre vytváranie trojuholníkov. Zatiaľ vytvoríme len prvú polovicu modulu, kde bude definovaná trieda pre všeobecný trojuholník, v ktorej využijeme aj vyššie spomenuté špeciálne metódy.

Naša trieda sa bude volať Triangle a bude zapísaná v súbore triangle.py. V tejto triede môžeme pre trojuholník definovať niekoľko metód a statickú premennú pre pomenovanie trojuholníka. Zo špeciálnych metód definujeme konštruktor, deštruktor a metódu __str__(), ktorá nám v užívateľsky prívetivom formáte vráti hodnoty trojuholníka.

Ďalej vytvoríme použijeme metódu na overenie, či je trojuholník reálny, čiže či platí trojuholníková nerovnosť, metódu na získanie rozmerov trojuholníka a metódu, na výpočet obvodu trojuholníka (keďže vzorec pre obvod trojuholníka platí pre všetky trojuholníky). Vyzerať by to mohlo takto:

class Triangle:
    """ Všeobecná trieda pre trojuholník """
    name = "trojuholník"

    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c
        if not self.is_real():
            raise ValueError("Zadané neplatné hodnoty.")
        print("Vytvorený", self.name, "so stranami:", *self.get_dims())

    def __str__(self):
        """ Interpretácia objektu ako dátového typu string """
        name = self.name.capitalize()
        return "{} so stranami {} {} {}".format(name, *self.get_dims())

    def __del__(self):
        """ Metóda volaná pri zmazaní objektu (pomocou funkcie del()) """
        print(self, "bol odstránený.") # print(self) == print(self.__str__())

    def get_dims(self):
        """ Metóda, ktorá vráti tuple s rozmermi """
        return self.a, self.b, self.c

    def get_perimeter(self):
        """ Vráti obvod trojuholníka """
        return sum(self.get_dims())

    def is_real(self):
        """ Vráti True ak a+b > c, a+c > b, b+c > a. Inak False. """
        a, b, c = self.get_dims()
        if a+b > c and a+c > b and b+c > a:
            return True
        else:
            return False

V príklade som sa snažil použiť len funkcie, o ktorých som už písal v minulých častiach, avšak pre zabránenie vytvoreniu nereálneho trojuholníka som použil raise ValueError("Zadané neplatné hodnoty."), čo spôsobí vyvolanie chyby a zabráni tak vytvoreniu objektu. O chybách a výnimkách som písal už v staršej časti, ale veľa užitočného sa samozrejme dočítate aj v oficiálnej dokumentácii. Poďme si teraz predviesť fungovanie tohto kódu v interpreteri:

$ python3 -q
>>> import triangle
>>> first = triangle.Triangle(2, 3, 4)
Vytvorený trojuholník so stranami: 2 3 4
>>> second = triangle.Triangle(4, 5, 5)
Vytvorený trojuholník so stranami: 4 5 5
>>> print(first)
Trojuholník so stranami 2 3 4
>>> print(first.get_perimeter())
9
>>> del(first)
Trojuholník so stranami 2 3 4 bol odstránený.
>>> unreal = triangle.Triangle(1, 2, 3) # nesplní podmienku 1+2 > 3, takže chyba
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "~/Python 10/triangle/triangle.py", line 8, in __init__
    raise ValueError("Zadané neplatné hodnoty.")
ValueError: Zadané neplatné hodnoty.
>>> print(unreal) # počas vytvárania sa stala chyba, objekt bude odstránený
Trojuholník so stranami 1 2 3 bol odstránený.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'unreal' is not defined

Dedenie

Základnú triedu a zároveň aj modul pre vytváranie trojuholníkov by sme mali. Teraz budeme tento modul importovať a pomocou dedenia z triedy Triangle vytvoríme triedy špecifických trojuholníkov, rovnoramenného a rovnostranného. Pri dedení do argumentu triedy pri vytváraní triedy pridáme triedu, z ktorej chceme dediť: class Dedič(Trieda).

Ako vlastne to dedenie funguje? Dediaca trieda dedí všetky metódy a vlastnosti z určenej triedy. Okrem zdedených metód a vlastností môžeme triede zadefinovať nové metódy, alebo aj prepísať tie zdedené.

Vytvorme si teda nový modul, ktorý si bude importovať ten predchádzajúci modul, a z triedy Triangle budeme pomocou dedenia vytvárať triedy pre už spomenuté špecifické trojuholníky.

Najprv si vytvoríme triedu pre rovnoramenný (angl. Isosceles) trojuholník. Tomu predefinujeme konštruktor tak, aby mal len 2 parametre, čiže 2 strany – základňu a ramená, pričom vieme, že ramená rovnoramenného trojuholníka sú dve a sú rovnako dlhé. Triede rovnako pridáme metódu pre výpočet obsahu rovnoramenného trojuholníka, tj. základňa*výška/2. Pre vypočítanie výšky si takisto vytvoríme vlastnú metódu, pričom nemôžeme zabudnúť na importovanie math modulu, pre využitie odmocniny.

Z triedy pre rovnoramenný trojuholník potom zdedíme ešte špecifickejší rovnostranný trojuholník, kde už len prepíšeme konštruktor tak, aby jeho parametrom bola len jedna strana trojuholníka.

Ja som obe triedy vložil do nového súboru triangles.py:

import math
from triangle import Triangle


class Isosceles(Triangle):
    """ Rovnoramenný trojuholník """
    name = "rovnoramenný trojuholník" 

    def __init__(self, a, b):
        self.a = a
        self.b = self.c = b
        if not self.is_real():
            raise ValueError("Zadané neplatné hodnoty.")
        print("Vytvorený", self.name, "so stranami:", *self.get_dims()) 

    def get_altitude(self):
        """ Vráti výšku trojuholníka """
        return math.sqrt(self.c**2-(self.b/2)**2)

    def get_surface(self):
        """ Vráti obsah trojuholníka """
        return self.a*self.get_altitude()/2


class Equilateral(Isosceles):
    """ Rovnostranný trojuholník """
    name = "rovnostranný trojuholník"

    def __init__(self, a):
        self.a = self.b = self.c = a
        if not self.is_real():
            raise ValueError("Zadané neplatné hodnoty.")
        print("Vytvorený", self.name, "so stranami:", *self.get_dims())

Znovu si skúsime tento modul importovať v interpreteri a vytvoriť si niekoľko trojuholníkov:

$ ls
triangle.py  triangles.py
$ python3 -q
>>> import triangles
>>> dir(triangles)
['Equilateral', 'Isosceles', 'Triangle', '__builtins__', '__cached__', '__doc__', 
'__file__', '__loader__', '__name__', '__package__', '__spec__', 'math']
>>> a = triangles.Triangle(5, 4, 7)
Vytvorený trojuholník so stranami: 5 4 7
>>> b = triangles.Isosceles(1, 3)
Vytvorený rovnoramenný trojuholník so stranami: 1 3 3
>>> c = triangles.Equilateral(8)
Vytvorený rovnostranný trojuholník so stranami: 8 8 8
>>> print("\n".join((str(a), str(b), str(c))))
Trojuholník so stranami 5 4 7
Rovnoramenný trojuholník so stranami 1 3 3
Rovnostranný trojuholník so stranami 8 8 8
>>> b.get_surface()
1.299038105676658
>>> c.get_surface()
27.712812921102035
>>> del(a, c)
Trojuholník so stranami 5 4 7 bol odstránený.
Rovnostranný trojuholník so stranami 8 8 8 bol odstránený.
>>> b.get_altitude()
2.598076211353316
>>> del(b)
Rovnoramenný trojuholník so stranami 1 3 3 bol odstránený.

Záver

Tak, ukázali sme si, ako pri objektovom programovaní v Pythone funguje dedenie a spomenuli sme si pár špeciálnych metód. Dúfam, že je všetko dostatočne pochopiteľné, no samozrejme, v prípade dotazov píšte do komentárov.

Diskuze (2) Nahoru