22. diel - Abstraktné triedy v Pythone

V predchádzajúcom cvičení, Riešené úlohy k 18.-21. lekcii OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V nasledujúcom tutoriáli objektového programovania v Pythone sa budeme zaoberať abstraktnými triedami. Vysvetlíme si ich účel aj praktické využitie.

Abstraktné triedy v Pythone

Povedzme, že robíme napríklad aplikáciu ZOO, kde máme niekoľko rôznych zvierat. Každé zviera má nejaké meno, váhu a má metódu na pohyb. Keďže budú tieto atribúty aj metóda na všetkých zvieratách, ponúka sa pripraviť spoločnú triedu predka.

Kód by potom vyzeral napríklad takto:

class Animal:
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight

    def move(self):
        pass


class Dolphin(Animal):
    def move(self):
        print("Swimming...")


class Bird(Animal):
    def move(self):
        print("Flying...")

Dáva v tejto chvíli zmysel vytvoriť inštanciu triedy Animal ? Ani veľmi nie, nie je vôbec jasné, čo by sa malo stať, keď zavoláme metódu na pohyb. Ešte horšie by to bolo s metódou, ktorá má niečo vracať.

Takej triede, ako je naša Animal, sa hovorí abstraktná trieda. Je to trieda, pri ktorej nemá zmysel vytvoriť inštanciu, pretože sama nemá definované nejaké správanie. To má definovať až jej potomok. Je to z toho dôvodu, že je všeobecná. Zviera bude však vždy konkrétne (teda nejaký potomok, napr. Dolphin). Nikdy nebudeme chcieť, ani potrebovať, vytvoriť inštanciu triedy Animal. Chceme tiež donútiť potomkov tejto triedy, aby si metódu pre pohyb implementovali po svojom – napríklad hrochy, až na vzácne výnimky dané skôr okolnosťami, nedokážu lietať :-D

Abstraktná trieda je trieda, ktorá obsahuje aspoň jednu abstraktnú metódu. Abstraktná metóda je metóda, ktorá má deklaráciu, ale neobsahuje implementáciu.

Tvorba abstraktnej triedy

Abstraktnú triedu vytvoríme tak, že zdedíme triedu ABC (skratka z Abstract Base Class). Trieda ABC sa nachádza v module abc. Na označenie metódy ako "abstraktné" v Pythone slúži dekorátor @abstractmethod. Použitím dekorátora zaistíme, že táto metóda musí byť predefinovaná (alebo, ako sa často hovorí, "prekrytá") v akejkoľvek konkrétnej (neabstraktnej) podtriede, ktorá dedí z abstraktnej triedy.

Nasledujúci kód spadne, pretože sa v ňom pokúšame vytvoriť inštanciu všeobecného zvieraťa:

  • #!/usr/bin/env python3
    from abc import ABC, abstractmethod
    class Animal(ABC):
    def __init__(self, name, weight):
    self.name = name
    self.weight = weight
    @abstractmethod
    def move(self):
    pass
    animal = Animal("Elizabeth", 42)
    הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

    Vo výstupe uvidíme chybovú hlášku:

    Abstract class error:
    Can't instantiate abstract class Animal with abstract method move

    Abstraktná metóda

    Ako sme už povedali, abstraktná metóda síce má deklaráciu, ale neobsahuje implementáciu. Každú metódu, ktorú v abstraktnej triede označíme dekorátorom @abstractmethod, musíme predefinovať v akejkoľvek triede, ktorá z tejto abstraktnej triedy dedí. Pokiaľ túto metódu nepredefinujeme v triede potomka, Python vyhodí TypeError pri pokuse vytvoriť takú inštanciu:

    Klikni pre editáciu
    • class Animal(ABC):
          def __init__(self, name, weight):
              self.name = name
              self.weight = weight
      
          @abstractmethod
          def move(self):
              pass
      
      
      class Dog(Animal):
          # move() - method is not overridden
          pass
      
      
      try:
          d = Dog("Rex", 30)  # This will raise an error because Dog does not override the abstract method move()
      except TypeError as e:
          print(e)
      
      • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

      Vo výstupe uvidíme chybovú hlášku:

      Abstract class error:
      Can't instantiate abstract class Dog with abstract method move

      Toto je jeden z hlavných účelov abstraktných tried - nútenie konkrétnych tried na implementáciu určitých metód. To zaručuje, že všetky triedy dediace z danej abstraktnej triedy budú mať rovnakú sadu metód, hoci ich implementácia sa môže líšiť.

      Abstraktné triedy a metódy sú kľúčovým konceptom v objektovo orientovanom programovaní a umožňujú nám vytvárať robustnejší a lepšie organizovaný kód.

      Vytvorme teraz inštanciu triedy Dolphin, ktorá metódy preťažuje:

      Klikni pre editáciu
      • from abc import ABC, abstractmethod
        
        class Animal(ABC):
            def __init__(self, name, weight):
                self.name = name
                self.weight = weight
        
            @abstractmethod
            def move(self):
                pass
        
        
        class Dolphin(Animal):
            # Here we "override" the "move()" method from the abstract class "Animal".
            # Overriding means that in the subclass we define a method with the same name,
            # which takes over the functionality of the original method and can extend or completely replace it.
            def move(self):
                print("Swimming...")
        
        
        dolphin = Dolphin("Paul", 1500)
        dolphin.move()
        
        • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

        Výstup v konzole:

        Correct output:
        Swimming...

        Abstraktná trieda ako rozhranie (interface)

        V niektorých programovacích jazykoch, ako je napríklad Java alebo C#, sa abstraktné triedy, ktoré majú iba abstraktné metódy, nazývajú rozhranie (alebo interface). Tieto jazyky umožňujú triede dediť z jednej triedy a implementovať viac rozhrania.

        V Pythone však takýto explicitný koncept rozhrania neexistuje. Namiesto toho Python podporuje viacnásobnú dedičnosť, čo znamená , že trieda môže dediť z viac ako jednej triedy. Vďaka tomu môžeme v Pythone použiť abstraktné triedy podobným spôsobom ako rozhranie v iných jazykoch:

        from abc import ABC, abstractmethod
        
        class Flying(ABC):
            @abstractmethod
            def fly(self):
                pass
        
        class Swimming(ABC):
            @abstractmethod
            def swim(self):
                pass
        
        class Bird(Flying):
            def fly(self):
                print("I am a bird and I fly...")
        
        class Dolphin(Swimming):
            def swim(self):
                print("I am a dolphin and I swim...")
        
        class FlyingDolphin(Dolphin, Flying):
            def fly(self):
                print("I am a dolphin and I fly...")
        
        bird = Bird()
        dolphin = FlyingDolphin()
        
        bird.fly()
        dolphin.swim()
        dolphin.fly()

        V konzole potom uvidíme:

        Abstract classes as interfaces:
        I am a bird and I fly...
        I am a dolphin and I swim...
        I am a dolphin and I fly...

        V tomto príklade fungujú triedy Flying a Swimming ako rozhrania, ktoré definujú metódy fly() a swim(). Trieda Bird implementuje rozhranie Flying a trieda Dolphin implementuje rozhranie Swimming. Trieda FlyingDolphin potom dedí z oboch týchto rozhraní a implementuje obe metódy.

        Keď vytvoríme inštanciu triedy Bird a FlyingDolphin, obe tieto inštancie vedia volať metódu fly(). Inštancia triedy FlyingDolphin vie volať aj metódu swim(), pretože dedí túto funkcionalitu z triedy Dolphin.

        Hoci Python nemá explicitný koncept rozhrania ako takých, tento prístup nám umožňuje využiť podobnú funkcionalitu a vytvárať kód, ktorý je dobre štruktúrovaný a ľahko rozšíriteľný.

        Metód resolution order

        Jednou z kľúčových vecí pri práci s abstraktnými triedami a preťažovaní metód je, že keď trieda dedí z viacerých tried a preťažuje metódu, je táto metóda preddefinovaná v súlade s poslednou triedou v zozname rodičovských tried. To je dôvod, prečo v našom príklade inštancie triedy FlyingDolphin používa metódu fly() definovanú priamo v triede FlyingDolphin, a nie tú, ktorá je definovaná v triede Bird.

        Tento koncept sa nazýva Method Resolution Order (MRO), alebo aj "riešenie poradia metód" v preklade do slovenčiny. MRO v Pythone určuje poradie, v akom sa prehľadávajú rodičovské triedy pri hľadaní metódy, keď je táto metóda volaná na inštanciu triedy.

        Predstavme si napríklad, že máme tri triedy - A, B a C - kde B dedí od A a C dedí od B. Ak by sme chceli vytvoriť objekt z triedy C a volať metódu, ktorú definujú všetky tri triedy, MRO určí, ktorá z týchto metód bude volaná.

        V Pythone je MRO určený pravidlom zvaným C3 Linearization, alebo tiež "Lelouch's rule". Toto pravidlo určuje jednoznačné poradie tried v hierarchii dedenia, ktoré umožňuje Pythonu konzistentne a predvídateľne určovať, akú metódu použiť v prípade, že existuje viac možných implementácií rovnakej metódy v rôznych rodičovských triedach.

        Poradie MRO

        Ak chceme zistiť poradie MRO pre konkrétnu triedu v Pythone, použijeme vstavanú metódu mro():

        class First:
            def test(self):
                print("First")
        
        class Second:
            def test(self):
                print("Second")
        
        class Third(Second, First):
            pass
        
        print(Third.mro()) #  Determining the MRO for the class Third

        V konzole potom uvidíme nasledujúci výstup:

        Output MRO:
        [<class '__main__.Third'>, <class '__main__.Second'>, <class '__main__.First'>, <class 'object'>]

        Ako vidíme, Python vyhľadá metódy v triede Third, potom v triede Second a potom v triede First. To znamená, že ak je metóda test() volaná na inštanciu triedy Third, bude najprv hľadaná v triede Third, potom v triede Second a nakoniec v triede First:

        third = Third()
        third.test()

        Vo výstupe uvidíme:

        Output MRO:
        Second

        To je pre dnešnú lekciu všetko.

        V budúcej lekcii, Najčastejšie chyby Python nováčikov - Vieš pomenovať objekty, si ukážeme najčastejšie chyby začiatočníkov v Pythone ohľadom pomenovania tried, metód a atribútov.


         

        Ako sa ti páči článok?
        Pred uložením hodnotenia, popíš prosím autorovi, čo je zleZnakov 0 z 50-500
        Predchádzajúci článok
        Riešené úlohy k 18.-21. lekcii OOP v Pythone
        Všetky články v sekcii
        Objektovo orientované programovanie v Pythone
        Preskočiť článok
        (neodporúčame)
        Najčastejšie chyby Python nováčikov - Vieš pomenovať objekty
        Článok pre vás napísal Martin Macura
        Avatar
        Užívateľské hodnotenie:
        3 hlasov
        Aktivity