Co je __init__ v Pythonu? [With Examples]

Chcete se ponořit do světa objektově orientovaného programování v Pythonu? Začněte objevováním metody __init__.

V tomto průvodci prozkoumáme základy tříd a objektů v Pythonu, abychom následně detailněji pochopili účel metody __init__.

Po prostudování tohoto článku byste měli být schopni zodpovědět následující otázky:

  • Co jsou to instanční proměnné, označované též jako atributy instance?
  • Jak metoda __init__ usnadňuje inicializaci instančních atributů?
  • Jak definovat výchozí hodnoty pro atributy?
  • Jak můžeme využít metody tříd jako konstruktory pro vytváření objektů?

Pojďme na to!

Třídy a objekty v Pythonu

Třídy představují základ objektově orientovaného paradigmatu v Pythonu. Umožňují nám definovat struktury s atributy a metodami, čímž propojujeme data a operace s nimi související.

Jakmile třídu vytvoříme, můžeme ji považovat za návrh nebo šablonu pro vytváření konkrétních objektů, takzvaných instancí.

👩‍🏫 Ukázkový příklad! Navrhneme třídu Employee. Každý objekt této třídy bude reprezentovat zaměstnance s atributy:

  • full_name: celé jméno zaměstnance ve formátu jméno a příjmení
  • emp_id: unikátní ID zaměstnance
  • department: oddělení, do kterého zaměstnanec spadá
  • experience: délka praxe v letech

Co to vlastně znamená? 🤔

Každý zaměstnanec, kterého budeme evidovat, bude specifickou instancí třídy Employee. Každý z těchto objektů bude mít jedinečné hodnoty pro full_name, emp_id, department a experience.

Tyto atributy se rovněž nazývají instanční proměnné. Pojmy atribut a instanční proměnná budeme používat zaměnitelně.

Než se pustíme do přidávání atributů, definujeme prázdnou třídu zaměstnance:

class Employee:
    pass

Použití klíčového slova pass (jako zástupného symbolu) nám pomáhá vyhnout se chybám při spuštění skriptu.

I když současná verze třídy Employee není příliš praktická, jedná se o validní třídu. Můžeme tedy vytvářet objekty této třídy:

employee_1 = Employee()

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

Atributy můžeme také přidávat a iniciovat přímo, jak ukazuje příklad:

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

Nicméně, tento způsob přidávání atributů instancí je neefektivní a náchylný k chybám. Navíc nám neumožňuje využít třídu jako univerzální šablonu pro vytváření objektů. Právě zde vstupuje do hry metoda __init__.

Význam metody __init__ v Pythonu

Měli bychom mít možnost inicializovat instanční proměnné ihned při vytváření objektu. A právě metoda __init__ nám v tom pomáhá. Tato metoda se automaticky volá při vytváření nového objektu třídy, aby se nastavily počáteční hodnoty instančních proměnných.

Pokud máte zkušenosti s programováním v jazyce jako C++, možná si všimnete, že metoda __init__ plní obdobnou funkci jako konstruktory.

Definování metody __init__

Přidejme metodu __init__ do třídy Employee:

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 odkazuje na konkrétní instanci třídy a pomocí self.attribute inicializujeme daný atribut na hodnotu z pravé strany.

Nyní můžeme vytvářet objekty například takto:

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

Když vypíšeme objekty zaměstnanců, nezískáme žádné smysluplné informace kromě toho, ke které třídě patří. Pojďme proto přidat metodu __repr__, která definuje reprezentativní řetězec pro třídu:

 def __repr__(self):
        return f"{self.full_name},{self.emp_id} from {self.department} with {self.experience} years of experience."

Po přidání __repr__ do třídy Employee získáme:

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} from {self.department} with {self.experience} years of experience."

Nyní mají objekty zaměstnanců užitečný reprezentativní řetězec:

print(employee_2)
# Output: Bella Joy,M007 from Marketing with 3 years of experience.

Důležité konvence

Předtím, než budeme pokračovat dále, shrňme si několik důležitých bodů:

  • Jako první parametr v metodě __init__ používáme self, abychom odkazovali na samotnou instanci třídy. Používáme také self.attribute_name pro inicializaci jednotlivých atributů. Je zvykem používat právě self, ačkoliv lze použít jakékoli jiné jméno.
  • Při definici metody __init__ se snažíme, aby názvy parametrů v definici __init__ odpovídaly názvům atributů. To zlepšuje čitelnost kódu.

Přidávání výchozích hodnot atributů

V dosavadních příkladech byly všechny atributy povinné. To znamená, že vytvoření objektu se podařilo pouze tehdy, když jsme konstruktoru předali hodnoty pro všechna pole.

Zkusme vytvořit instanci objektu třídy Employee bez předání hodnoty pro atribut experience:

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

Zobrazí se následující chyba:

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'

Pokud však chceme, aby některé atributy byly nepovinné, můžeme jim při definici metody __init__ přiřadit výchozí hodnoty.

V následujícím příkladu nastavíme výchozí hodnotu atributu experience na 0:

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} from {self.department} with {self.experience} years of experience."

Objekt employee_3 je vytvořen bez hodnoty pro atribut experience. Použije se tedy výchozí hodnota 0.

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

Alternativní konstruktory tříd pomocí metod tříd

Zatím jsme se seznámili s definováním metody __init__ a nastavováním výchozích hodnot atributů. Také víme, že musíme konstruktoru předat hodnoty pro všechny povinné atributy.

V některých případech ale mohou být hodnoty pro instanční proměnné (atributy) dostupné v jiné datové struktuře, jako je tuple, slovník, nebo JSON.

Jak si s tím poradit?

Uvažujme následující případ. Předpokládejme, že máme hodnoty pro instanční proměnné uložené ve slovníku:

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

Hodnoty z tohoto slovníku můžeme získat například takto:

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

Následně můžeme vytvořit objekt předáním těchto hodnot konstruktoru:

employee_4 = Employee(name, id, dept, exp)
print(employee_4)
# Output: Fanny Walker,H203 from HR with 2 years of experience.

Pamatujte, že tento postup musíte opakovat pro každý nový objekt, který chcete vytvořit. Takovýto přístup není příliš efektivní a je možné to udělat lépe. Jak?

V Pythonu můžeme použít metody tříd jako konstruktory pro vytváření objektů dané třídy. K definování metody třídy používáme dekorátor @classmethod.

Pojďme si tedy definovat metodu, která zpracuje slovník, získá hodnoty instančních proměnných a využije je pro konstrukci objektů Employee:

    @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)

Když potřebujeme vytvářet objekty na základě dat ze slovníku, můžeme využít metodu třídy from_dict().

💡 Všimněte si použití cls v metodě třídy namísto self. Stejně jako používáme self pro odkazování na instanci, cls se používá pro odkazování na třídu. Metody třídy jsou vázány na třídu, nikoliv na objekty.

Když tedy voláme metodu třídy from_dict() pro vytváření objektů, voláme ji na třídě 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 from Sales with 3 years of experience.

Nyní, pokud máme slovník pro každého z N zaměstnanců, můžeme použít metodu třídy from_dict() jako konstruktor pro vytváření instancí objektů – bez nutnosti manuálně získávat hodnoty instančních proměnných ze slovníku.

📝 Poznámka k proměnným tříd

Zde jsme definovali metodu třídy, která je vázána na třídu a nikoli na konkrétní instance. Podobně jako metody třídy, můžeme mít i proměnné třídy.

Stejně jako metody třídy, proměnné třídy jsou vázány na třídu a ne na instanci. Pokud má atribut pevnou hodnotu pro všechny instance třídy, je vhodné jej definovat jako proměnnou třídy.

Často kladené otázky

1. Proč potřebujeme metodu __init__ v Pythonu?

Metoda __init__ v definici třídy nám umožňuje inicializovat atributy (instanční proměnné) všech instancí dané třídy. Metoda __init__ je volána vždy, když se vytvoří nová instance třídy.

2. Je možné mít ve třídě Pythonu více metod __init__?

Cílem více metod __init__ ve třídě Pythonu by bylo poskytnout více konstruktorů pro vytváření instancí objektů. Nicméně, není možné definovat více metod __init__. Pokud definujete více metod __init__, druhá a nejnovější implementace přepíše tu první. Můžete však použít dekorátor @classmethod pro definování metod tříd, které lze použít jako konstruktory pro vytváření instancí objektů.

3. Co se stane, pokud nedefinujeme metodu __init__ ve třídě?

Pokud nedefinujete metodu __init__, stále je možné vytvářet instance objektů. Nicméně, budete muset ručně přidávat proměnné instance a přiřazovat jim hodnoty. Nebudete moci předávat hodnoty pro instanční proměnné v konstruktoru. To je nejen náchylné k chybám, ale také to maří účel mít třídu jako návrh, ze kterého vytváříme instance objektů.

4. Je možné nastavit výchozí hodnoty argumentů v metodě __init__?

Ano, je možné nastavit výchozí hodnoty pro jeden nebo více atributů při definování metody __init__. Poskytování výchozích hodnot umožňuje učinit tyto atributy v konstruktoru volitelnými. Atributy převezmou výchozí hodnoty, pokud hodnoty pro ně nezadáme v konstruktoru.

5. Je možné modifikovat atributy i mimo metodu __init__?

Ano, hodnotu atributu můžete kdykoli aktualizovat i mimo metodu __init__. Po vytvoření konkrétní instance můžete do ní také dynamicky přidávat nové atributy.

Závěr

V tomto tutoriálu jsme se naučili, jak používat metodu __init__ k inicializaci hodnot instančních proměnných. I když je tento koncept jednoduchý, může se opakovat, zvláště pokud máte mnoho atributů.

Pokud vás toto téma zaujalo, můžete prozkoumat modul dataclasses. V Pythonu 3.7 a novějších můžete využít vestavěný modul datových tříd pro vytváření tříd, které ukládají data. Kromě výchozí implementace __init__ a dalších běžně používaných metod, přináší datové třídy spoustu skvělých funkcí, jako jsou typové anotace, komplexní výchozí hodnoty a optimalizace.

Dále se dozvíte více o if __name__ == '__main__' v Pythonu.