Знайомство з методом __init__ у Python для об’єктно-орієнтованого програмування
Бажаєте розпочати свій шлях у світ об’єктно-орієнтованого дизайну з Python? Зробіть перші кроки вже сьогодні, вивчивши метод __init__.
У цьому посібнику ми розглянемо основні концепції класів та об’єктів у Python, а потім заглибимось у вивчення методу __init__.
Після ознайомлення з цим матеріалом, ви зможете відповісти на такі питання:
- Що таке змінні екземпляра, або атрибути екземпляра?
- Яким чином метод `__init__` допомагає ініціалізувати атрибути екземпляра?
- Як можна встановити значення за замовчуванням для атрибутів?
- Як використовувати методи класу як конструктори для створення об’єктів?
Розпочнемо!
Класи та об’єкти у Python
Класи є основою об’єктно-орієнтованого програмування в Python. Вони дозволяють створювати структури, що об’єднують дані (атрибути) та функції (методи), які працюють з цими даними.
Створивши клас, його можна використовувати як шаблон для створення об’єктів (екземплярів).
👩🏫Приклад! Створимо клас `Employee`, де кожен об’єкт матиме такі атрибути:
- `full_name`: повне ім’я співробітника (ім’я та прізвище)
- `emp_id`: ідентифікатор співробітника
- `department`: відділ, в якому він працює
- `experience`: кількість років досвіду
Що це означає? 🤔
Кожен співробітник буде окремим екземпляром або об’єктом класу `Employee`. Кожен з них матиме власні значення `full_name`, `emp_id`, `department` та `experience`.
Ці атрибути також називають змінними екземпляра. Ми будемо використовувати терміни “атрибути” та “змінні екземпляра” як взаємозамінні.
Згодом ми додамо атрибути. Зараз створимо клас `Employee` наступним чином:
class Employee:
pass
Використання `pass` (заповнювача) дозволяє уникнути помилок під час виконання сценарію.
Хоча поточна версія класу `Employee` є досить простою, вона вже є дійсним класом. Отже, ми можемо створювати об’єкти класу `Employee`:
employee_1 = Employee()
print(employee_1)
#Вивід: <__main__.Employee object at 0x00FEE7F0>
Ми також можемо додавати атрибути та ініціалізувати їх значеннями, як показано нижче:
employee_1.full_name="Amy Bell"
employee_1.department="HR"
Однак такий спосіб додавання атрибутів екземпляра неефективний та схильний до помилок. Крім того, це не дозволяє використовувати клас як шаблон для створення об’єктів. Тут на допомогу приходить метод `__init__`.
Роль методу `__init__` у класах Python
Для нас важливо мати можливість ініціалізувати змінні екземпляра під час створення об’єкта. Метод `__init__` допомагає нам у цьому. Він викликається автоматично при створенні нового об’єкта класу, для ініціалізації його атрибутів.
Якщо ви вже програмували на C++, ви побачите схожість з конструкторами.
Визначення методу `__init__`
Додамо метод `__init__` до класу `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
Параметр `self` посилається на сам екземпляр класу, а `self.attribute` ініціалізує атрибут екземпляра значенням, яке знаходиться справа.
Тепер ми можемо створювати об’єкти наступним чином:
employee_2 = Employee('Bella Joy','M007','Marketing',3)
print(employee_2)
# Вивід: <__main__.Employee object at 0x017F88B0>
Коли ми друкуємо об’єкти співробітників, ми не отримуємо корисної інформації, крім назви класу, до якого вони належать. Давайте додамо метод `__repr__`, який визначає рядкове представлення класу:
def __repr__(self):
return f"{self.full_name},{self.emp_id} з {self.department}, досвід - {self.experience} років."
Після додавання методу `__repr__` до класу `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
def __repr__(self):
return f"{self.full_name},{self.emp_id} з {self.department}, досвід - {self.experience} років."
Тепер об’єкти співробітників мають корисне рядкове представлення:
print(employee_2)
#Вивід: Bella Joy,M007 з Marketing, досвід - 3 років.
Деякі правила
Перш ніж продовжувати, варто звернути увагу на кілька моментів:
- Ми використали `self` як перший параметр в методі `__init__` для посилання на екземпляр класу. `self.attribute_name` використовується для ініціалізації атрибутів. Рекомендується використовувати `self` (хоча ви можете використовувати будь-яку іншу назву).
- При визначенні методу `__init__` ми встановлюємо імена параметрів у визначеннях `__init__` відповідно до імен атрибутів, для покращення читабельності.
Встановлення значень за замовчуванням для атрибутів
У прикладі, який ми розглянули, всі атрибути є обов’язковими. Це означає, що створення об’єкта буде успішним, тільки якщо ми передамо значення для всіх полів у конструктор.
Спробуйте створити екземпляр класу `Employee`, не передаючи значення для атрибута `experience`:
employee_3 = Employee('Jake Lee','E001','Engineering')
Ви отримаєте наступну помилку:
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'
Але якщо ви хочете зробити певні атрибути необов’язковими, ви можете задати значення за замовчуванням для них при визначенні методу `__init__`.
Тут ми встановлюємо значення за замовчуванням 0 для атрибута `experience`:
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} з {self.department}, досвід - {self.experience} років."
Об’єкт `employee_3` створюється без значення для атрибута `experience`; для нього буде використано значення за замовчуванням 0.
employee_3 = Employee('Jake Lee','E001','Engineering')
print(employee_3.experience)
#Вивід: 0
Альтернативні конструктори класу з використанням методів класу
Наразі ми розглянули визначення методу `__init__` і встановлення значень за замовчуванням для атрибутів. Ми також знаємо, що нам потрібно передати значення для необхідних атрибутів в конструкторі.
Однак іноді значення змінних екземпляра (атрибутів) можуть бути доступні в інших структурах даних, наприклад, у кортежі, словнику або рядку JSON.
Що ж нам тоді робити?
Розглянемо приклад. Припустимо, що значення змінних екземпляра знаходяться у словнику Python:
dict_fanny = {'name':'Fanny Walker','id':'H203','dept':'HR','exp':2}
Ми можемо звернутися до словника і отримати всі атрибути наступним чином:
name = dict_fanny['name']
id = dict_fanny['id']
dept = dict_fanny['dept']
exp = dict_fanny['exp']
Після цього можна створити об’єкт, передаючи ці значення в конструктор класу:
employee_4 = Employee(name, id, dept, exp)
print(employee_4)
#Вивід: Fanny Walker,H203 з HR, досвід - 2 років.
Пам’ятайте: вам доведеться робити це для кожного нового об’єкта, який ви створюєте. Такий підхід неефективний, і ми можемо зробити краще. Але як?
У Python ми можемо використовувати методи класу як конструктори для створення об’єктів класу. Щоб створити метод класу, ми використовуємо декоратор `@classmethod`.
Давайте визначимо метод, який буде аналізувати словник, отримувати значення змінних екземпляра та використовувати їх для створення об’єктів `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)
Коли нам потрібно створити об’єкти, використовуючи дані зі словника, ми можемо використовувати метод класу `from_dict()`.
💡Зверніть увагу на використання `cls` у методі класу замість `self`. Так само, як ми використовуємо `self` для посилання на екземпляр, `cls` використовується для посилання на клас. Методи класу прив’язані до класу, а не до об’єктів.
Отже, коли ми викликаємо метод класу `from_dict()` для створення об’єктів, ми викликаємо його для класу `Employee`:
emp_dict = {'name':'Tia Bell','id':'S270','dept':'Sales','exp':3}
employee_5 = Employee.from_dict(emp_dict)
print(employee_5)
#Вивід: Tia Bell,S270 з Sales, досвід - 3 років.
Тепер, якщо у нас є словник для кожного з n співробітників, ми можемо використовувати метод класу `from_dict()` як конструктор для створення екземплярів об’єктів, без необхідності окремо отримувати значення змінних екземпляра зі словника.
📝Примітка щодо змінних класу
Тут ми визначили метод класу, який прив’язаний до класу, а не до окремих екземплярів. Подібно до методів класу, ми також можемо мати змінні класу.
Як і методи класу, змінні класу прив’язані до класу, а не до екземпляра. Коли атрибут приймає фіксоване значення для всіх екземплярів класу, ми можемо розглянути можливість визначення їх як змінних класу.
Часті питання
1. Навіщо потрібен метод `__init__` у Python?
Метод `__init__` у визначенні класу дозволяє нам ініціалізувати атрибути або змінні екземпляра всіх екземплярів класу. Метод `__init__` викликається кожен раз, коли створюється новий екземпляр класу.
2. Чи можна мати декілька методів `__init__` у класі Python?
Метою наявності декількох методів `__init__` у класі Python є надання кількох конструкторів, які створюють екземпляри об’єктів. Але ви не можете визначити кілька методів `__init__`. Якщо ви визначите кілька методів `__init__`, то остання реалізація перезапише попередню. Однак ви можете використовувати декоратор `@classmethod` для визначення методів класу, які можна використовувати як конструктори для створення об’єктів.
3. Що станеться, якщо не визначити метод `__init__` у класі?
Якщо ви не визначите метод `__init__`, ви все одно зможете створювати об’єкти. Однак вам доведеться вручну додавати змінні екземпляра та призначати значення кожній з них. Ви не зможете передати значення змінних екземпляра у конструктор. Це не тільки є помилковим підходом, але й нівелює мету використання класу як шаблону для створення об’єктів.
4. Чи можна використовувати значення за замовчуванням для аргументів у методі `__init__`?
Так, при визначенні методу `__init__` можна задавати значення за замовчуванням для одного або декількох атрибутів. Це допомагає зробити атрибути необов’язковими в конструкторі. Атрибути приймають значення за замовчуванням, якщо ви не передаєте їх значення в конструкторі.
5. Чи можна змінювати атрибути поза методом `__init__`?
Так, ви завжди можете оновити значення атрибута за межами методу `__init__`. Ви також можете динамічно додавати нові атрибути до екземпляра, після того, як він вже був створений.
Висновок
У цьому посібнику ми навчилися використовувати метод `__init__` для ініціалізації значень змінних екземплярів. Хоча цей механізм простий, його використання може виявитися рутинним, особливо коли у вас є багато атрибутів.
Якщо вам цікаво, ви можете дослідити модуль класів даних. У Python 3.7 та новіших версіях ви можете використовувати вбудований модуль класів даних для створення класів даних, які зберігають дані. На додаток до реалізації за замовчуванням `__init__` та інших типових методів, вони мають багато цікавих функцій для підказок типів, складних значень за замовчуванням та оптимізації.
Далі ви можете дізнатися більше про вираз `if __name__ == ‘__main__’` у Python.