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
Mammaldziedziczył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.
class myGUI: def __init__(self, window): self.window = window #title #geometry class masterFrame(window): pass window = Tk() my_gui = myGUI(window) window.mainloop()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