Konstruktor w Pythonie

Zanim zanurzymy się w tematach związanych z tajemniczym konceptem dziedziczenia, zapoznamy się z elementem klasy nazywanym konstruktorem, który będziemy 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ą), która wywoływana jest 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 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(), i poprzez 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 argumentów w implementacji metody __init__ czyli: def __init__(self). Warto zanaczyć, że jeśli w __init__ nie ma argumentó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 przyjmuje jeden parametr (tak jak u nas name), bez podawania wartości 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, in 
    adam = 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. 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 czyli Human(name).)

Na koniec warto wspomnieć jeszcze o dwóch rzeczach:

  • metoda __init__ nie może nic zwracać za pomocą wyrażenia return. 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żenia return:

    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ędzie self, czyli def __init__(self):.

Teraz, gdy już znamy podstawy o konstruktorze, możemy przenieść się do głównego tematu tej sekcji – dziedziczenia.

4 komentarze

  1. 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

    Avatar Dawid Kowalczyk
    1. 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?

      class Human:
         def set_name(self, name):
             self.name = name
      
         def speak(self):
             print(f'I can speak! My name is {self.name}!')
      

      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 pola self.name) oraz wywołanie metody speak wyglądałoby w następujący sposób:

      adam = Human()
      adam.set_name('Adam')
      adam.speak()
      

      Tutaj widzimy, że stworzenie obiektu adam odrobinę się skomplikowało. Dodatkowo tutaj musimy zawsze pamiętać o wywołaniu metody set_name przed metodą speak. W przeciwnym wypadku zostaniemy obdarowaniu wyjątkiem:

      AttributeError: 'Human' object has no attribute 'name'
      

      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,

      Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *