Co to jest __init__ w Pythonie? [With Examples]

Czy pragniesz rozpocząć swoją przygodę z programowaniem obiektowym w Pythonie? Już dziś masz szansę postawić pierwsze kroki, zagłębiając się w tajniki metody __init__ tego języka.

W niniejszym przewodniku szczegółowo omówimy podstawowe koncepcje klas i obiektów w Pythonie, by następnie przejść do szczegółowej analizy działania metody __init__.

Po lekturze tego artykułu będziesz w stanie bez trudu odpowiedzieć na poniższe pytania:

  • Czym dokładnie są zmienne instancji, inaczej atrybuty instancji?
  • Jak metoda init ułatwia proces inicjalizacji atrybutów instancji?
  • W jaki sposób możemy przypisać wartości domyślne atrybutom?
  • Jak wykorzystać metody klasowe jako konstruktory do generowania obiektów?

Zaczynajmy zatem!

Klasy i obiekty w Pythonie

Klasy stanowią fundament programowania obiektowego w Pythonie. Dzięki nim możemy tworzyć struktury, w których definiujemy zarówno dane (atrybuty), jak i operacje na nich wykonywane (metody), łącząc w ten sposób informacje z działaniami.

Gdy klasa jest już zdefiniowana, staje się swoistym planem lub szablonem, na podstawie którego generujemy obiekty, czyli jej instancje.

👩‍🏫 Czas na przykład! Wyobraźmy sobie klasę Pracownik, gdzie każdy obiekt tej klasy charakteryzuje się następującymi atrybutami:

  • full_name: pełne imię i nazwisko pracownika, w formacie „Imię Nazwisko”
  • emp_id: unikalny identyfikator pracownika
  • dział: nazwa działu, do którego pracownik przynależy
  • doświadczenie: liczba lat doświadczenia zawodowego

Co to konkretnie oznacza? 🤔

Każdy pracownik będzie reprezentowany jako odrębna instancja (obiekt) klasy Pracownik. Każdy z tych obiektów będzie posiadał unikatowe wartości dla atrybutów takich jak full_name, emp_id, dział i doświadczenie.

Te atrybuty są również znane jako zmienne instancji, a terminy „atrybut” i „zmienna instancji” będziemy stosować zamiennie.

Za chwilę zajmiemy się dodawaniem atrybutów. Na razie stwórzmy klasę Employee w następujący sposób:

class Employee:
    pass

Użycie pass (jako symbolu zastępczego) pozwala nam uniknąć błędów podczas wykonywania skryptu.

Mimo że aktualna wersja klasy Pracownik nie jest jeszcze funkcjonalna, to wciąż jest poprawną klasą. Możemy zatem tworzyć obiekty (instancje) tej klasy:

employee_1 = Employee()

print(employee_1)
#Output: <__main__.Employee object at 0x00FEE7F0>

Możemy również dodać atrybuty i przypisać im wartości, jak pokazano poniżej:

employee_1.full_name="Amy Bell"
employee_1.department="HR"

Jednak takie podejście do dodawania atrybutów instancji jest nie tylko mało efektywne, ale również narażone na błędy. Ponadto, nie pozwala ono na pełne wykorzystanie klasy jako szablonu do tworzenia obiektów. W tym miejscu do akcji wkracza metoda __init__.

Zrozumienie roli metody __init__ w klasie Pythona

Naszym celem jest możliwość inicjalizacji zmiennych instancji już w momencie tworzenia obiektu. Metoda __init__ idealnie się do tego nadaje. Jest ona automatycznie wywoływana za każdym razem, gdy tworzymy nowy obiekt danej klasy, umożliwiając tym samym ustawienie początkowych wartości jego zmiennych.

Dla osób znających C++, metoda __init__ działa bardzo podobnie do konstruktorów w tym języku.

Definiowanie metody __init__

Dodajmy teraz metodę __init__ do naszej klasy Pracownik:

class Employee:
    def __init__(self, full_name,emp_id,department,experience):
        self.full_name = full_name
        self.emp_id = emp_id
        self.department = department
        self.experience = experience

Parametr self odnosi się do konkretnej instancji klasy, a zapis self.attribute inicjalizuje atrybut instancji wartością z prawej strony.

Od teraz możemy tworzyć obiekty w następujący sposób:

employee_2 = Employee('Bella Joy','M007','Marketing',3)
print(employee_2)
# Output: <__main__.Employee object at 0x017F88B0>

Jak widać, wypisanie obiektu nie daje zbyt wielu użytecznych informacji poza tym, do jakiej klasy on należy. Dodajmy zatem metodę __repr__, która zdefiniuje reprezentację tekstową klasy:

 def __repr__(self):
        return f"{self.full_name},{self.emp_id} z działu {self.department} z {self.experience} latami doświadczenia."

Po dodaniu __repr__ do klasy Pracownik otrzymujemy:

class Employee:
    def __init__(self, full_name,emp_id,department,experience):
        self.full_name = full_name
        self.emp_id = emp_id
        self.department = department
        self.experience = experience
    
     def __repr__(self):
        return f"{self.full_name},{self.emp_id} z działu {self.department} z {self.experience} latami doświadczenia."

Teraz obiekty klasy Pracownik mają bardziej zrozumiałą reprezentację tekstową:

print(employee_2)
# Output: Bella Joy,M007 z działu Marketing z 3 latami doświadczenia.

Kilka konwencji

Zanim przejdziemy dalej, warto zwrócić uwagę na kilka kwestii:

  • Użyliśmy self jako pierwszego parametru w metodzie __init__, aby odwołać się do instancji klasy. Następnie użyliśmy self.nazwa_atrybutu do inicjalizacji atrybutów. Używanie self jest powszechnie przyjętą konwencją (można użyć dowolnej innej nazwy, ale nie jest to zalecane).
  • Podczas definiowania metody __init__, nazwy parametrów odpowiadają nazwom atrybutów. Podnosi to czytelność kodu.

Jak dodać domyślne wartości atrybutów

W przykładzie, który stworzyliśmy do tej pory, wszystkie atrybuty były wymagane. Oznacza to, że utworzenie obiektu jest możliwe tylko wtedy, gdy przekażemy wartości dla wszystkich pól konstruktora.

Spróbujmy utworzyć instancję klasy Pracownik bez podawania wartości dla atrybutu doświadczenie:

employee_3 = Employee('Jake Lee','E001','Engineering')

Otrzymamy następujący błąd:

Traceback (most recent call last):
  File "main.py", line 22, in <module>
    employee_3 = Employee('Jake Lee','E001','Engineering')
TypeError: __init__() missing 1 required positional argument: 'experience'

Jeżeli jednak chcemy, aby niektóre atrybuty były opcjonalne, możemy to osiągnąć przez przypisanie im domyślnych wartości podczas definiowania metody __init__.

W naszym przypadku, przypiszmy domyślną wartość 0 dla atrybutu doświadczenie:

class Employee:
    def __init__(self, full_name,emp_id,department,experience=0):
        self.full_name = full_name
        self.emp_id = emp_id
        self.department = department
        self.experience = experience
    
     def __repr__(self):
        return f"{self.full_name},{self.emp_id} z działu {self.department} z {self.experience} latami doświadczenia."

Teraz obiekt pracownik_3 zostanie utworzony bez wartości dla atrybutu doświadczenie, a jego domyślna wartość wyniesie 0:

employee_3 = Employee('Jake Lee','E001','Engineering')
print(employee_3.experience)
# Output: 0

Alternatywne konstruktory klas z wykorzystaniem metod klasowych

Do tej pory nauczyliśmy się, jak definiować metodę __init__ oraz ustawiać domyślne wartości atrybutów. Wiemy również, że musimy przekazać wartości wymaganych atrybutów konstruktorowi.

Czasami jednak dane, które mają zostać przypisane jako wartości zmiennych instancji (atrybutów), mogą być dostępne w innej formie, np. jako krotka, słownik lub łańcuch JSON.

Co w takiej sytuacji powinniśmy zrobić?

Rozważmy przykład. Załóżmy, że wartości zmiennych instancji mamy dostępne w słowniku Pythona:

dict_fanny = {'name':'Fanny Walker','id':'H203','dept':'HR','exp':2}

Możemy oczywiście wyciągnąć wszystkie wartości z tego słownika w ten sposób:

name = dict_fanny['name']
id = dict_fanny['id']
dept = dict_fanny['dept']
exp = dict_fanny['exp']

Następnie możemy utworzyć obiekt, przekazując te wartości do konstruktora klasy:

employee_4 = Employee(name, id, dept, exp)
print(employee_4)
# Output: Fanny Walker,H203 z działu HR z 2 latami doświadczenia.

Jednak to rozwiązanie nie jest optymalne, gdyż musielibyśmy wykonywać te same czynności dla każdego tworzonego obiektu. Istnieje bardziej efektywny sposób! Ale jak to zrobić?

W Pythonie możemy używać metod klasowych jako konstruktorów obiektów. Aby utworzyć metodę klasową, używamy dekoratora @classmethod.

Zdefiniujmy metodę, która analizuje słownik, pobiera wartości zmiennych instancji i wykorzystuje je do tworzenia obiektów pracownika.

    @classmethod
    def from_dict(cls,data_dict):
        full_name = data_dict['name']
        emp_id = data_dict['id']
        department = data_dict['dept']
        experience = data_dict['exp']
        return cls(full_name, emp_id, department, experience)

Od teraz, gdy będziemy chcieli utworzyć obiekt na podstawie danych ze słownika, możemy użyć metody klasowej from_dict().

💡 Zauważ, że w metodzie klasowej używamy cls zamiast self. Tak jak self odnosi się do instancji, tak cls odnosi się do klasy. Ponadto, metody klasowe są powiązane z klasą, a nie z jej obiektami.

Gdy wywołujemy metodę klasową from_dict() w celu utworzenia obiektów, wywołujemy ją na klasie Employee:

emp_dict = {'name':'Tia Bell','id':'S270','dept':'Sales','exp':3}
employee_5 = Employee.from_dict(emp_dict)
print(employee_5)
# Output: Tia Bell,S270 z działu Sales z 3 latami doświadczenia.

Teraz, mając słownik dla każdego z n pracowników, możemy skorzystać z metody klasowej from_dict() jako konstruktora do tworzenia obiektów, bez konieczności ręcznego wyciągania wartości zmiennych ze słownika.

📝 Uwaga o zmiennych klasowych

Zdefiniowaliśmy metodę klasową, która jest powiązana z klasą, a nie z konkretnymi instancjami. Podobnie jak w przypadku metod klasowych, możemy również definiować zmienne klasowe.

Zmienne klasowe są powiązane z klasą, a nie z jej instancjami. Gdy dany atrybut ma stałą wartość dla wszystkich obiektów klasy, warto zdefiniować go jako zmienną klasową.

Najczęściej zadawane pytania

1. Dlaczego metoda __init__ jest potrzebna w Pythonie?

Metoda __init__ w definicji klasy umożliwia inicjalizację atrybutów (zmiennych instancji) dla każdego obiektu klasy. Jest ona wywoływana automatycznie przy każdym utworzeniu nowej instancji.

2. Czy w klasie Pythona można zdefiniować wiele metod __init__?

Teoretycznie, wielokrotne zdefiniowanie metody __init__ w klasie miałoby umożliwić posiadanie wielu konstruktorów. W praktyce, nie można zdefiniować wielu metod __init__, ponieważ druga i każda kolejna implementacja nadpisuje poprzednią. Jednakże, można użyć dekoratora @classmethod do zdefiniowania metod klasowych, które działają jak alternatywne konstruktory.

3. Co się stanie, jeśli nie zdefiniujemy metody __init__ w klasie?

Brak metody __init__ nie uniemożliwia tworzenia obiektów. Jednakże będziemy musieli ręcznie dodawać zmienne instancji i przypisywać im wartości dla każdego obiektu. Nie będziemy mogli przekazywać wartości zmiennych instancji w konstruktorze. Takie podejście jest nie tylko podatne na błędy, ale również podważa ideę wykorzystania klasy jako wzorca do tworzenia obiektów.

4. Czy można użyć domyślnych wartości argumentów w metodzie __init__?

Tak, podczas definiowania metody __init__ można podać wartości domyślne dla wybranych atrybutów. Użycie wartości domyślnych sprawia, że atrybuty stają się opcjonalne. Jeśli podczas tworzenia obiektu nie podamy wartości takiego atrybutu, zostanie użyta wartość domyślna.

5. Czy atrybuty można modyfikować poza metodą __init__?

Tak, wartość atrybutu można zmieniać poza metodą __init__. Można również dynamicznie dodawać nowe atrybuty do instancji już po jej utworzeniu.

Podsumowanie

W niniejszym poradniku nauczyliśmy się, jak używać metody __init__ do inicjalizacji wartości zmiennych instancji. Mimo swojej prostoty, w przypadku dużej liczby atrybutów, proces ten może być powtarzalny.

Osobom zainteresowanym, polecam zapoznanie się z modułem dataclasses. W Pythonie w wersji 3.7 i nowszych, istnieje wbudowany moduł klas danych, które upraszczają proces tworzenia klas przechowujących dane. Poza domyślną implementacją __init__ i innych często wykorzystywanych metod, klasy te posiadają również wiele użytecznych funkcji, m.in. podpowiedzi typów, złożone wartości domyślne i optymalizacje.

Następnie warto dowiedzieć się więcej o konstrukcji if __name__ == '__main__' w Pythonie.


newsblog.pl