Powrót do: Podstawy Testów Automatycznych w Selenium i Python cz. 5 – Profesjonalna konfiguracja projektu
Konstruktor w Pythonie
Zanim zanurzymy się w tematach związanych z tajemniczym konceptem dziedziczenia, zapoznamy się z elementem klasy nazywanym konstruktorem. Będziemy go wykorzystywać bardzo często i na różne sposoby.
Zapewne do tej pory spotkaliście się już z metodą __init__
(chociażby w lekcji Refaktoryzacja – oczyszczanie kodu – Dwa słowa o instancji i self w Pythonie). Jest to specjalna metoda, dosyć powszechnie nazywana konstruktorem (chociaż poprawniej byłoby ją nazywać metodą inicjalizacyjną). Wywoływana jest ona jako pierwsza podczas inicjalizacji nowego obiektu. Służy do ustawienia różnych wartości i przygotowania danego obiektu do naszych celów. Od nas zależy co umieścimy w __init__
i czy w ogóle tę metodę zdefiniujemy, gdyż __init__
jest opcjonalny.
Dla lepszego zobrazowania metody __init__
posłużymy się przykładem dwóch prostych klas:
class Human: def __init__(self): print('New Human was born!') def speak(self): print('I can speak!') class Animal: def speak(self): print('I can not speak!') # Class usage examples print("Let's create Adam!") adam = Human() print("Let's create a dog!") dog = Animal() print('Now all speak!') adam.speak() dog.speak()
Pierwsza klasa Human posiada __init__
, natomiast klasa Animal go nie posiada. Obie konstrukcje są poprawne. Jaki wynik otrzymamy po uruchomieniu skryptu?
Let's create Adam! New Human was born! Let's create a dog! Now all speak! I can speak! I can not speak!
Widzimy, że zaraz po inicjalizacji obiektu Adam wykonywana jest metoda __init__
. Nie musieliśmy nic więcej robić tylko utworzyć nowy obiekt adam = Human()
.Poprzez takie użycie klasy Human()
, __init__
wywołał się automatycznie.
W powyższych przykładach używaliśmy konstruktora bezargumentowego, nazywa on się tak ponieważ, poza niezbędnym self
nie wymagamy innych parametrów przy użyciu metody __init__
czyli: def __init__(self)
.
Przypomnijmy, że parametr to coś co dajemy przy deklaracji funkcji (np. def add(nuber1, nuber2)
) a argument to coś co dodajemy przy użyciu funkcji (np. add(2, 3)
).
Warto zanaczyć, że jeśli w __init__
nie przyjmuje parametrów, to przy użyciu naszej klasy, też nie będziemy ich potrzebować, czyli adam = Human()
. Możemy to nazwać bezargumentową klasą inicjalizującą).
Jak wygląda przekazywanie argumentów i definiowanie parametrów? Identycznie jak w przypadku metod w klasie – wystarczy dopisać parametry w nawiasie 😉 W poniższym przykładzie dodajemy parametr name
a następnie przypisujemy go do zmiennej z prefiksem self
co sprawi, że wszystkie metody w klasie będą miały do niej dostęp. Skorzystamy z tego i dodamy tę zmienną do tekstu wypisywanego na konsolę:
class Human: def __init__(self, name): self.name = name print(f'New Human was born! His name is {self.name}!') def speak(self): print(f'I can speak! My name is {self.name}!') class Animal: def speak(self): print('I can not speak!') print("Let's create Adam!") adam = Human('Adam') print("Let's create a dog!") dog = Animal() print('Now all speak!') adam.speak() dog.speak()
Po uruchomieniu dostaniemy następujący wynik:
Let's create Adam! New Human was born! His name is Adam! Let's create a dog! Now all speak! I can speak! My name is Adam! I can not speak!
Tutaj może pojawić się pytanie
Co się stanie gdy spróbujemy stworzyć obiekt, którego konstruktor wymaga jednego parametru (tak jak u nas
name
), bez podawania wartości dla tego parametru?
Sprawdźmy na poniższym przykładzie:
class Human: def __init__(self, name): self.name = name print(f'New Human was born! His name is {self.name}!') def speak(self): print(f'I can speak! My name is {self.name}!') print("Let's create Adam!") adam = Human()
Jaki będzie wynik po uruchomieniu?
Let's create Adam! Traceback (most recent call last): File "C:/Users/jaktestowac/.PyCharmCE2018.2/config/scratches/init_fun_2.py", line 10, inadam = Human() TypeError: __init__() missing 1 required positional argument: 'name'
Dostajemy wyraźną informację o błędzie, a dokładniej o braku wartości dla parametru name
(w skrócie argumentu). Czyli możemy zauważyć, że dodanie parametru do metody inicjalizującej tj konstruktora powoduje, że ten parametr jest wymagany przy korzystaniu z naszej klasy:Human(name)
.)
Na koniec warto wspomnieć jeszcze o dwóch rzeczach:
- metoda
__init__
nie może zwracać wyniku za pomocą wyrażeniareturn
. Dla poniższego kodu:class Human: def __init__(self, name): self.name = name print(f'New Human was born! His name is {self.name}!') return name def speak(self): print(f'I can speak! My name is {self.name}!') print("Let's create Adam") adam = Human("Adam")
dostaniemy błąd, informujący nas, że metoda
__init__
nie powinna zawierać wyrażeniareturn
:Let's create Adam! Traceback (most recent call last): New Human was born! His name is Adam! File "C:/Users/jaktestowac/.PyCharmCE2018.2/config/scratches/init_fun_2.py", line 11, in
adam = Human('Adam') TypeError: __init__() should return None, not 'str' - z racji, że metoda
__init__
jest częścią klasy i związana jest z daną instancją obiektu, to zawsze jej pierwszym parametrem będzieself
, czylidef __init__(self):
.
Teraz, gdy już znamy podstawy o konstruktorze, możemy przenieść się do głównego tematu tej sekcji – dziedziczenia.
Dzieki Krzysiek to dużo mi pomogło
Nie ma sprawy 😉
czyli init zawsze zostanie wykonany jako pierwszy? i to jest cale clue konstruktora? żeby zmusić program do wykonywania metod w odpowiedniej kolejności ? Bo nie zrozumiałem koncepcji
Hej,
Tak, konstruktor (lub metoda inicjalizacyjna)
__init__
zawsze wykona się jako pierwsza. Bardzo często potrzebujemy aby nasz obiekt już na starcie (czyli zaraz po stworzeniu, przygotowaniu) miał jakieś konkretne wartości. Dzięki temu zaraz po stworzeniu danego obiektu jest on gotowy do użycia.Odwołując się do powyższego przykładu z klasą
Human
– jak wyglądałaby klasa bez konstruktora?Klasa ta potrzebuje mieć pole
self.name
, więc z racji że nie ma konstruktora, to musimy dla tego pola (i ustawiania jego wartości) stworzyć dodatkową metodędef set_name(self, name):
. Tworzenie obiektu z takiej bezkonstruktorowej klasy, ustawienie imienia (czyli polaself.name
) oraz wywołanie metodyspeak
wyglądałoby w następujący sposób:Tutaj widzimy, że stworzenie obiektu
adam
odrobinę się skomplikowało. Dodatkowo tutaj musimy zawsze pamiętać o wywołaniu metodyset_name
przed metodąspeak
. W przeciwnym wypadku zostaniemy obdarowaniu wyjątkiem:Brak konstruktora narzuca na nas dodatkowy obowiązek wywołania odpowiednich metod i zatroszczenia się o zmienne i ich wartości. Przy naszych własnych klasach może nie wydawać się to aż tak bolesne, ale przy pracy z cudzym kodem wyszłaby kłopotliwość i uciążliwość takiego rozwiązania. Mam nadzieję, że udało mi się odrobinę rozwiać wątpliwości 😉
Pozdrawiam,