Dziedziczenie w praktyce

Przygotowanie klas

Zacznijmy od przygotowania sobie pliku scratches i stworzenia klasy bazowej Animal. W naszej klasie umieścimy na razie jedynie prosty konstruktor (czyli __init__) oraz jedną z metod z diagramu, czyli increase_age. W każdej z wymienionych metod umieścimy prosty print, który będzie nam wypisywał na konsolę informację o każdej z metod. Czyli w rezultacie nasza klasa przybierze postać:

class Animal:
   def __init__(self):
       print('Animal!')

   def increase_age(self):
       print('increase_age!')

Teraz powtórzmy wszystkie kroki dla klasy Mammal, którą umieścimy w tym samym pliku. Zgodnie z diagramem musimy w niej utworzyć metodę introduce_yourself.

class Mammal:
   def __init__(self):
       print('Mammal!')

   def introduce_yourself(self):
       print('introduce_yourself!')

W tym momencie może się pojawić pytanie:

Jak zrobić aby klasa Mammal dziedziczyła po klasie Animal?

Wystarczy dodać informację po jakiej klasie ma dziedziczyć klasa Mammal:

class Mammal(Animal):

czyli umieszczamy nazwę klasy bazowej w definicji klasy pochodnej (w nawiasach).

class Animal:
   def __init__(self):
       print('Animal!')

   def increase_age(self):
       print('increase_age!')


class Mammal(Animal):
   def __init__(self):
       print('Mammal!')

   def introduce_yourself(self):
       print('introduce_yourself!')

Wykorzystanie klas i uruchomienie kodu

Można pod tym kodem wykonać polecenia, które pokażą, że możemy skorzystać z metody z klasy po której dziedziczymy czyli w naszym przypadku metody increase_age()

dog = Mammal()
dog.increase_age()

Co zakończy się wynikiem:

Mammal!
increase_age!

Pięknie! Użyliśmy klasy Mammal i skorzystaliśmy z metody z klasy Animal

Inicjalizacja klasy bazowej w podrzędnej

Dodatkowo, aby zainicjalizować zmienne z klasy bazowej w klasie podrzędnej musimy wywołać konstruktor klasy bazowej Animal.__init__(self) w konstruktorze klasy pochodnej.
Brzmi zawile ale zaraz zobaczymy to w akcji. Nasza klasa przyjmie następującą postać:

class Mammal(Animal):
   def __init__(self):
       Animal.__init__(self)
       print('Mammal!')

   def introduce_yourself(self):
       print('introduce_yourself!')

Co przy takim kodzie:

dog = Mammal()
dog.increase_age()

Da nam wynik:

Animal!
Mammal!
increase_age!

Jak widzimy dzięki temu obiekt utworzony z klasy Mammal wywołuje automatycznie konstruktor klasy Animal.

TIP: Dlaczego wywołanie konstruktora klasy bazowej Animal.__init__(self) jest opcjonalne? Wywołanie go zależy od dwóch rzeczy.

Pierwszą jest czy konstruktor klasy bazowej wymaga podania argumentów. W naszym obecnym przypadku, konstruktor w klasie Animal def __init__(self) nie wymaga żadnych parametrów i robi tylko print(), dlatego możemy pominąć tą linię. Gdyby jednak konstruktor w klasie Animal przyjmował jakieś parametry np. name (czyli miał postać def __init__(self, name)), to musielibyśmy go wywołać w klasie pochodnej jako Animal.__init__(self, name) – tym przykładem zajmiemy się w kolejnych lekcjach.

Drugim warunkiem kiedy musimy wywołać konstruktor klasy bazowej, jest moment gdy w konstruktorze klasy bazowej inicjalizujemy jakieś zmienne bądź wykonujemy operacje, których wynik będzie potrzebny w klasie pochodnej. Poniżej prosty przykład obrazujący potencjalne problemy.

class Animal:
   def __init__(self):
       print('Animal!')
       self.some_value = 1

   def increase_age(self):
       print('increase_age!')


class Mammal(Animal):
   def __init__(self):
       print('Mammal!')
       print(self.some_value)

   def introduce_yourself(self):
       print('introduce_yourself!')


mammal_1 = Mammal()

Przy próbie uruchomienia powyższego skryptu otrzymamy błąd, gdyż zmienna self.some_value nie została zainicjalizowana:

AttributeError: 'Mammal' object has no attribute 'some_value'

Usuńmy kod:

dog = Mammal()
dog.increase_age()

Pełny test dziedziczenia

Teraz zrobimy testy całościowe napisanego kodu. Na samym dole naszego skryptu dodajmy:

    Inicjalizację obiektu mammal_1, który będzie typu Mammal.
    Wywołanie metody introduce_yourself() na obiekcie mammal_1.
    Wywołanie metody increase_age() na obiekcie mammal_1.

Miejmy na uwadze, że pierwsza metoda introduce_yourself() pochodzi z klasy pochodnej (czyli Mammal), a increase_age() – z klasy bazowej Animal.

Cały kod przyjmie następującą postać:

class Animal:
   def __init__(self):
       print('Animal!')

   def increase_age(self):
       print('increase_age!')


class Mammal(Animal):
   def __init__(self):
       Animal.__init__(self)
       print('Mammal!')

   def introduce_yourself(self):
       print('introduce_yourself!')


mammal_1 = Mammal()
mammal_1.introduce_yourself()
mammal_1.increase_age()

Po jego uruchomieniu otrzymamy:

Animal!
Mammal!
introduce_yourself!
increase_age!

Jakie wnioski możemy z tego wyciągnąć?

    Możemy zaobserwować, że konstruktor klasy bazowej faktycznie został wywołany – pojawił się napis Animal! na konsoli.
    Metoda increase_age() jest bez problemu dostępna z klasy pochodnej.

Zapoznaliśmy się z odrobiną teorii oraz odrobiną praktyki z dziedziczeniem w roli głównej. Teraz przyszedł czas na krótkie zadania, które ma na celu utrwalić zdobytą wiedzę 😉

4 komentarze

  1. Czyli tak naprawdę dziedziczenie odbywa się tylko dla metod ale bez konstruktora? którego, jeżeli chcemy dziedziczyć to musimy zainicjalizowac jako Animal.__init__(self) i dopiero wtedy mamy dostęp do jego zmiennych?

    Avatar Dawid Kowalczyk
    1. Jest to poprawne stwierdzenie, z niewielką poprawkę – zamiast dziedziczyć konstruktor (klasy bazowej) to raczej po prostu musimy go ręcznie wywołać (w klasie pochodnej) mieć dostęp do zmiennych, które są inicjalizowane w konstruktorze 😉

      Krzysiek Kijas Krzysiek Kijas
    2. Szukam najlepszego sposobu na uporządkowanie kodu dla biblioteki tkinter. Chce stworzyć klasę bazową class Window:, class myGUI: w której to będą znajdowały się podstawowe metody tej biblioteki takie jak np. geometria okna (centrowanie okna, rozdzielczość, title itd.)

      Wszystkie inne klasy takie jak np. master_frame = LabelFrame będą i muszą dziedziczyć po klasie głównej.

      Jest wiele sposobów porządkowania kodu i trochę się w gubie, ale ten wydaje mi się najlepszy.

      class myGUI:
         def __init__(self, window):
            self.window = window
            #title
            #geometry
      
      class masterFrame(window):
         pass
      
      window = Tk()
      my_gui = myGUI(window)
      window.mainloop()
      
      Avatar lukas
      1. Hej,
        W projekcie dużo zależy od jego przeznaczenia – niektóre dobre praktyki, rozwiązania i podejścia sprawdzą się lepiej w specyficznych warunkach.

        Ogólnie przy projektowaniu architektury trzeba pamiętać o:
        – zasadzie SOLID
        – że czasem kompozycja jest znacznie lepsza od dziedziczenia

        Mogę też polecić zasoby, takie jak np. https://atechnovel.com/2019/04/05/leverage-automation-testing-with-design-patterns/
        gdzie znajdziesz dokładniejsze informacje na temat wzorców projektowych.
        Również w tym wątku na stackoverflow poruszają kwestie architektury: https://stackoverflow.com/questions/17466561/best-way-to-structure-a-tkinter-application
        Również w uporządkowaniu kodu może pomoże ten zasób: https://www.codegrepper.com/code-examples/python/use+frame+class+in+tkinter+example

        Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *