Перейти к основному содержимому

Конструктор __init__ и жизненный цикл объекта

В этой статье разбираем, как создаётся объект в Python и как правильно инициализировать его данные внутри конструктора __init__.

Что происходит при создании объекта

Когда вы пишете Класс(аргументы), Python делает три шага:

  1. создаёт пустой объект будущего класса;
  2. вызывает для него __init__(self, ...), передавая ваши аргументы;
  3. возвращает готовый объект.
class HockeyPlayer:
def __init__(self, name, age, team):
# self — это «этот самый объект», который только что создали
self.name = name
self.age = age
self.team = team


p = HockeyPlayer('Иван', 17, 'Метеор')
print(p.name, p.age, p.team)
Иван 17 Метеор

Важно: __init__ не должен ничего возвращать. Он обязан вернуть None (это делает Python автоматически). Попытка вернуть что‑то ещё приводит к ошибке.

class Bad:
def __init__(self):
return 123 # так делать нельзя

try:
Bad()
except TypeError as e:
print('Ошибка:', e)
Ошибка: __init__() should return None, not 'int'

Позиционные и именованные аргументы, значения по умолчанию

__init__ — обычная функция, поэтому у него работают все правила аргументов: позиционные, именованные, значения по умолчанию. Значения по умолчанию делают создание типовых объектов короче и нагляднее.

class HockeyPlayer:
def __init__(self, name, age=0, team='Без команды'):
self.name = name
self.age = age
self.team = team


# Можно позиционно
p1 = HockeyPlayer('Иван', 17, 'Метеор')
# Можно по именам — порядок уже не важен
p2 = HockeyPlayer(name='Павел', team='Вымпел', age=18)
# Можно опустить необязательные — подставятся значения по умолчанию
p3 = HockeyPlayer('Сергей')

print(p1.name, p1.age, p1.team)
print(p2.name, p2.age, p2.team)
print(p3.name, p3.age, p3.team)
Иван 17 Метеор
Павел 18 Вымпел
Сергей 0 Без команды

Базовая валидация прямо в __init__

Проверяйте входные данные там, где они появляются. Если что‑то не так — поднимите понятное исключение. Так вы раннее ловите ошибки и сохраняете «здоровье» объекта.

class HockeyPlayer:
def __init__(self, name, age, team):
if age < 0:
raise ValueError('Возраст не может быть отрицательным')
if not name:
raise ValueError('Имя должно быть непустым')
self.name = name
self.age = age
self.team = team


try:
HockeyPlayer('', 17, 'Метеор')
except ValueError as e:
print('Ошибка:', e)

try:
HockeyPlayer('Иван', -1, 'Метеор')
except ValueError as e:
print('Ошибка:', e)
Ошибка: Имя должно быть непустым
Ошибка: Возраст не может быть отрицательным

Осторожно: изменяемые значения по умолчанию

Главная ловушка Python‑функций — изменяемые значения по умолчанию (например, списки, словари). Они создаются один раз при определении функции и потом используются всеми вызовами. В __init__ это легко даёт «расшаренное» состояние между объектами.

Плохой вариант (делает один общий список для всех игроков):

class HockeyPlayer:
def __init__(self, name, achievements=[]): # так делать нельзя
self.name = name
self.achievements = achievements


a = HockeyPlayer('Иван')
b = HockeyPlayer('Павел')

a.achievements.append('Гол в финале')
print(a.achievements)
print(b.achievements) # внезапно тоже содержит то же достижение
['Гол в финале']
['Гол в финале']

Правильный приём — использовать None как «сентинел» и создавать новое значение внутри __init__.

class HockeyPlayer:
def __init__(self, name, achievements=None):
self.name = name
# Если не передали список — создаём новый, отдельный для каждого
self.achievements = list(achievements) if achievements is not None else []


a = HockeyPlayer('Иван')
b = HockeyPlayer('Павел')

a.achievements.append('Гол в финале')
print(a.achievements)
print(b.achievements) # теперь списки независимы
['Гол в финале']
[]

Вычисляемые и нормализованные поля

Иногда удобно привести входные данные к единому виду и посчитать производные поля прямо в __init__, чтобы остальной код работал проще.

class HockeyPlayer:
def __init__(self, name, surname, team):
# Нормализуем: делаем имя с заглавной буквы
self.name = name.strip().capitalize()
self.surname = surname.strip().capitalize()
# Сохраняем исходную команду, но также готовим «красивое» поле
self.team = team
self.full_name = f'{self.name} {self.surname}'


p = HockeyPlayer('иван', 'иванов', 'Метеор')
print(p.full_name)
Иван Иванов

Маленькая памятка

  • __init__ настраивает объект и ничего не возвращает.
  • Поддерживайте удобные аргументы: позиционные, именованные, значения по умолчанию.
  • Проверяйте входные данные там же и поднимайте понятные исключения.
  • Не используйте изменяемые значения по умолчанию — применяйте None и создавайте новые объекты внутри __init__.
  • При желании нормализуйте вход и вычисляйте производные поля — это упростит остальной код.