Linux E X P R E S

Facebook

Python 3 (9): Triedy a základy objektového programovania

python.png

V deviatej časti seriálu o programovaní v Python 3 si ukážeme, ako na objektové programovanie v tomto jazyku. Nezabudnime na to, že v Pythone je objekt patriaci nejakej triede všetko. My si ukážeme, ako vytvoriť vlastný objekt našej triedy.


Objektovo orientované programovanie

Objektovo orientované programovanie (OOP) je spôsob programovania, pri ktorom sú časti programu zoskupované do väčších celkov – objektov, ktoré majú svoje funkcie v OOP zvané metódy, svoje vlastnosti v podobe premenných atp.. Objekty sú vytvárané na základe preddefinovaných kritérií – tried. Jednou z hlavných výhod OOP je prehľadnejší kód a princíp podobný reálnemu svetu.

V reálnom svete máme rovnako objekty, ktoré sú navzájom podobné, čiže zdieľajú svoje metódy a vlastnosti. Príklad si uveďme na smartfónoch. Triedou, z ktorej budeme vytvárať objekty, jednotlivé modely, bude trieda smartfón. Tá bude mať preddefinované metódy ako volanie, ktoré na každom smartfóne funguje rovnako, posielanie SMS, prehliadanie internetu atp.

Jedná sa totiž o funkcie, ktoré obsahuje každý smartfón. Trieda smartfón bude mať ďalej svoje vlastnosti ako názov, displej, procesor, …, ktoré bude nutné zadať pri vytváraní objektu a pre každý môžu byť rozdielne. Podľa triedy smartfón teraz môžeme vytvárať objekty – modely.

Prvý model smartfónu bude mať napríklad 5palcový Full HD displej a 4jadrový 1,2GHz procesor. Druhý model bude mať 5,5palcový displej s rozlíšením 2K a 8jadrový 1,8GHz procesor. Oba smartfóny majú rovnaké funkcie – metódy, dajme tomu, že majú rovnaký operačný systém. Rozdielne sú však ich parametre – vlastnosti, ako spomenutý procesor, displej.

My si názorný príklad neskôr ukážeme na dvojrozmernom geometrickom telese obdĺžnik, teda, budeme vytvárať objekty – obdĺžniky.



OOP v Pythone

Skôr ako si ukážeme názorný príklad, vysvetlíme si ešte pár vecí a znovu spomeniem, že v programovacom jazyku Python je objektom všetko. Každé číslo, funkcia, reťazec… Tieto objekty sa rovnako tvoria z tried a majú svoje atribúty – metódy a vlastnosti. Znovu si teda ukážme, ako zistíme typ premennej:

>>> type("Reťazec")
<class 'str'>

Vidíme, že typom reťazca je trieda str (string), čiže objekt "Reťazec" bol vytvorený z triedy str. Táto trieda má svoje atribúty:

>>> dir(str)
['__add__', '__class__', …je tu toho veľa…, 'replace', 'rfind', 'rindex', 'rjust', 
'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 
'title', 'translate', 'upper', 'zfill']
>>> dir("Reťazec")
['__add__', '__class__', …je tu toho veľa…, 'replace', 'rfind', 'rindex', 'rjust', 
'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 
'title', 'translate', 'upper', 'zfill']
>>> dir("Reťazec") == dir(str)
True

Vidíme, že objekt "Reťazec" vytvorený z triedy str má rovnaké atribúty ako trieda, z ktorej bol vytvorený. Keby sme vytvorili ďalší objekt, ktorý by bol rovnako reťazcom, jeho atribúty by boli identické. Môžete si všimnúť metódy, ktoré sme už v predchádzajúcich dieloch využívali, pri manipulácií s dátovými typmi.

Tieto triedy ako str, int, float, list, function, NoneType atď. sú vstavané v Pythone, a automaticky sa z nich tvoria jednotlivé objekty (funkcie, čísla, zoznamy...) často ukladané do premenných.

Jednoduchá trieda a objekt

Pre vytvorenie triedy v Pythone sa používa kľúčové slovíčko class v kombinácii s názvom triedy, ktorý by podľa dohody mal začínať veľkým písmenom:

class Class:
    pass

Takto sme vytvorili triedu, ktorá je úplne prázdna a nerobí nič. Neznamená to však, že by sme nemohli vytvoriť objekt z takejto triedy. Ten vytvoríme zavolaním triedy podobne ako funkcie s tým, že jej obsah uložíme do premennej:

>>> class Class:
...  pass
... 
>>> obj = Class()
>>> type(obj)
<class '__main__.Class'>
>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', 
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', 
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Objekt obj je teraz objektom triedy Class, pričom tá je podtriedou hlavnej triedy programu, čiže podtriedou triedy __main__. Môžete vidieť, že síce sme v triede Class žiadne atribúty nedefinovali, nejaké tam sú. Ide o základné atribúty každej triedy, ktorým sa ale takmer nebudeme venovať.

Trieda s metódou

Metóda je pomenovanie pre funkciu pri OOP. Metóda vytvorená v triede bude dostupná vo všetkých objektoch vytvorených z danej triedy. Tá samozrejme môže mať rôzne parametre. Pre zavolanie metódy vytvoreného objektu používame syntax objekt.metóda(argumenty).

>>> class Class:
...  def method(x):
...   print("Ja som metóda, ktorej bol zadaný argument:", x)
... 
>>> obj = Class
>>> obj.method(42)
Ja som metóda, ktorej bol zadaný argument: 42
>>> dir(obj)
['__class__', …, '__weakref__', 'method']

Trieda s premennou

V triede môžeme vytvárať aj premenné, ktoré budú rovnako ako metódy dostupné vo všetkých objektoch tejto triedy.

>>> class Class:
...  name = "Meno"
... 
>>> obj = Class()
>>> print(obj.name)
Meno

self

Ak by sme v metóde objektu chceli pristupovať k jeho premenným, musíme mu istým spôsobom predať samého seba. To docielime použitím argumentu self. Ten odkazuje na objekt, v ktorom sa metóda nachádza. Použitie: self.názov_premennej:

>>> class Class:
...  name = "Meno"
...  def what_is_your_name(self):
...   print(self.name)
... 
>>> obj = Class()
>>> obj.what_is_your_name ()
Meno

Použitie self je dané konvenciou a všetky editory ho automaticky zvýrazňujú. Použiť sa však dá akákoľvek premenná, no určite sa to neodporúča.

Konštruktor – vytvárame unikátne objekty

Doteraz boli naše vytvorené objekty len klonmi nadradenej triedy, respektíve na danú triedu odkazovali (čítaj viac), pretože sa od triedy nijak nelíšili. Neoddeliteľnou súčasťou OOP je však vytváranie objektov, ktoré sú niečím špecifické, čo dosiahneme použitím konštruktoru. Trieda s konštuktorom vyzerá takto:

class Class:
    def __init__(self):
        pass

Ako to funguje? Konštruktor je špeciálna metóda __init__(), ktorá sa automaticky volá pri vytváraní objektu. Môže mať svoje ďalšie parametre, použité napríklad na zapísanie nejakých vlastností objektu. Argumenty tejto metóde potom predáme pri vytváraní objektu:

object = Class(argumenty)

Argumenty sú automaticky predané konštruktoru.

Prejdime teda na názorný príklad, ktorý som spomínal úvode článku. Vytvoríme si triedu pre obdĺžnik tak, aby sme pomocou nej mohli vytvárať rôzne objekty, čiže rôzne obdĺžniky, ktoré budú mať svoje rozmery. Pre tento príklad nebudem používať interpreter, ale samostatný súbor, ktorý budeme spúšťať.

class Rectangle:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        print("Obdĺžnik s rozmermi {}x{} bol vytvorený.".format(a, b))
    
    def get_dimensions(self):
        """ Vráti tuple s rozmermi: (a, b) """
        return self.a, self.b

rect1 = Rectangle(1, 5)
rect2 = Rectangle(20, 3)
squar = Rectangle(42, 42)

print("Rozmery rect1 sú {}x{}.".format(*rect1.get_dimensions()))
print("Rozmery rect2 sú {}x{}.".format(*rect2.get_dimensions()))
print("Rozmery squar sú {}x{}.".format(*squar.get_dimensions()))

Poznámka ku kódu: použitie hviezdičky pri *rect1.get_dimensions() spôsobí, že je n-tica vrátená touto funkciou rozdelená na viacero argumentov, podľa počtu prvkov v nej.

Ukážka:
.format(rect1.get_dimensions()) -> .format((1, 5))
.format(*rect1.get_dimensions()) -> .format(1, 5)

Toto použitie je možné pri všetkých iterovateľných dátových typoch.

Výstup:

$ python3 oop.py
Obdĺžnik s rozmermi 1x5 bol vytvorený.
Obdĺžnik s rozmermi 20x3 bol vytvorený.
Obdĺžnik s rozmermi 42x42 bol vytvorený.
Rozmery rect1 sú 1x5.
Rozmery rect2 sú 20x3.
Rozmery squar sú 42x42.

V príklade sme teraz z triedy Rectangle vytvárali objekty rect1, rect2squar s rôznymi rozmermi. Tie sa pri vytváraní objektu zapísali ako premenné ab, a rovnako sa vypísala hláška, že obdĺžnik s rozmermi xy bol vytvorený. Následne sme pomocou klasickej funkcie print tieto rozmery vypísali, pričom rozmery sme od každého objektu získali pomocou jeho metódy get_dimensions, ktorá pri zavolaní vrátila oba rozmery obalené v dátovom type tuple.

Skomplikovať si to teraz môžeme tým, že odlíšime špeciálny druh obdĺžnika – štvorec, pridáme vypočítanie obsahu a dĺžky uhlopriečky. Pre vypísanie týchto informácie potom použijeme cyklus for. Tak, tu to máme:

import math


class Rectangle:
    def __init__(self, a, b=None):
        self.a = a
        self.b = b if b else a
        if self.a == self.b:
            self.shape = "Štvorec"
        else:
            self.shape = "Obdĺžnik"
        print("{} s rozmermi {}x{} bol vytvorený.".format(
            self.shape, self.a, self.b))

    def get_dimensions(self):
        """ Vráti tuple s rozmermi: (a, b) """
        return self.a, self.b

    def get_surface(self):
        """ Vypočíta a vráti veľkosť povrchu """
        surface = self.a * self.b
        return surface

    def get_diagonal(self):
        """ Vypočíta a vráti dĺžku uhlopriečky """
        diagonal = math.sqrt(self.a**2 + self.b**2)
        return diagonal

rect1 = Rectangle(1, 5)
rect2 = Rectangle(20, 3)
squar = Rectangle(42)

variables = {"rect1": rect1, "rect2": rect2, "squar": squar}

text = """{shape} {name}
Rozmery: {a}x{b}
Obsah: {surface}
Dĺžka uhlopriečky: {diagonal}"""

for x in variables.keys():
    obj = variables[x]
    print(text.format(
        shape=obj.shape,
        name=x,
        a=obj.get_dimensions()[0],
        b=obj.get_dimensions()[1],
        surface=obj.get_surface(),
        diagonal=obj.get_diagonal()
    ))

Výstup:

$ python3 oop.py
Obdĺžnik s rozmermi 1x5 bol vytvorený.
Obdĺžnik s rozmermi 20x3 bol vytvorený.
Štvorec s rozmermi 42x42 bol vytvorený.
Štvorec squar
Rozmery: 42x42
Obsah: 1764
Dĺžka uhlopriečky: 59.39696961966999
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

V kóde je ešte celkom veľa vecí, ktoré by sa dali vylepšiť, ako napríklad nulový rozmer jednej zo strán. O vylepšenie sa môžete pokúsiť sami.

Záver

Pre tento diel je to z objektového programovania v Python 3 všetko. V ďalšej časti budeme v OOP pokračovať a pozrieme sa na dedičnosť, privátne metódy a ešte niektoré špeciálne metódy pri objektoch.

Diskuze (0) Nahoru