Конструктор __init__ и жизненный цикл объекта
В этой статье разбираем, как создаётся объект в Python и как правильно инициализировать его данные внутри конструктора __init__.
Что происходит при создании объекта
Когда вы пишете Класс(аргументы), Python делает три шага:
- создаёт пустой объект будущего класса;
- вызывает для него
__init__(self, ...), передавая ваши аргументы; - возвращает готовый объект.
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__. - При желании нормализуйте вход и вычисляйте производные поля — это упростит остальной код.