Powrót do: Podstawy Testów Automatycznych w Selenium i Python cz. 5 – Profesjonalna konfiguracja projektu
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 klasieAnimal
?
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
.
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:
introduce_yourself()
na obiekcie mammal_1.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ąć?
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ę 😉
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?
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 😉
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.
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