Как и в любом другом языке программирования, конструкторы в Python предназначены для инициализации новых объектов. Неправильное использование конструкторов может привести к различным проблемам в программе. Рассказываем подробнее.
Что такое конструктор
Конструктор — это метод, который используется для создания и инициализации объектов класса и определён в самом классе. Стандартный конструктор в Python обозначается как __init__
и имеет синтаксис:
def __init__(self):
Каждый раз, когда определяется метод объекта для класса, в качестве первого параметра используется self
— это ссылка на текущий объект. С помощью self
можно получить доступ к переменной и методу объекта. Не обязательно называть первый параметр self, можно дать ему любое имя, но оно должно быть указано первым параметром.
Если конструктор не определён, то есть не включён в какой-то класс или класс не объявлен, при создании объекта конструктор и инструкции в нём вызываются и выполняются автоматически. Такой конструктор пустой и не возвращает какое-либо значение, в том числе None, и не выполняет никаких задач, только инициализирует объект. Например, при выполнении кода
object = Example()
Python понимает, что obj
— объект класса Example
, и вызывает для создания объекта конструктор этого класса.
Какие бывают конструкторы
Как и в случае с неопределённым конструктором, если конструктор не объявлен в своём классе, Python вставит в код конструктор по умолчанию. Он сделает это во время компиляции, то есть в исходном файле .py такой конструктор может отсутствовать.
Конструктор без аргументов называется непараметризованным. Он имеет стандартный синтаксис и инициализирует каждый объект с одним и тем же набором значений:
class Media:
def __init__(self):
self.type = 'Журнал'
self.name = '"Код"'
def show(self):
print('Вид издания:', self.type, 'Название:', self.name)
media = Media()
media.show()
Конструктор с определёнными аргументами называется параметризованным. С его помощью во время создания объектов можно передавать им разные значения. Параметризованный конструктор может иметь любое количество аргументов: сколько объявим, столько и будет:
class Employee:
def __init__(self, name, surname, position):
self.name = name
self.surname = surname
self.position = position
def show(self):
print(self.name, self.surname, self.position)
ira = Employee('Ира', 'Михеева', 'корректор')
ira.show()
masha = Employee('Маша', 'Климентьева', 'Руководитель группы контент-менеджеров')
masha.show()
Инициализация параметров объекта
Чтобы избежать ошибок, инициализировать параметры нужно до вызова методов, которые могут зависеть от этих параметров.
Самая простая инициализация — когда параметры объекта инициализируются непосредственно значениями, переданными в конструктор:
class Editorial:
def __init__(self, name, surname):
self.name = name
self.surname = surname
def show(self):
print(self.name, self.surname)
mike = Editorial('Михаил', 'Полянин')
mike.show()
Ещё в конструктор можно добавить проверку значений:
class Employee:
def __init__(self, name, surname, since):
if not isinstance(name, str):
raise ValueError('Имя должно быть строкой')
if not (2019 <= since <= 2024):
raise ValueError('Год начала работы должен быть в диапазоне 2019–2024')
self.name = name
self.surname = surname
self.since = since
def show(self):
print(self.name, self.surname, self.since)
mike = Employee('Михаил', 'Полянин', 2019)
mike.show()
Можно задать значения по умолчанию для некоторых параметров, чтобы сделать их необязательными при создании объекта, — они будут использоваться, если не передать эти параметры конструктору во время создания объекта:
class Editorial:
def __init__(self, name, surname, position='автор'):
self.name = name
self.surname = surname
self.position = position
def show(self):
print(self.name, self.surname, self.position)
christina = Editorial('Кристина', 'Тульцева')
christina.show()
mike = Editorial('Михаил', 'Полянин', 'главный редактор')
mike.show()
Параметры могут быть не только простыми типами данных, но и сложными объектами, такими как списки, словари или даже представители других классов.
class Media:
def __init__(self, type, name):
self.type = type
self.name = name
class Editorial:
def __init__(self, name, surname, position, media):
self.name = name
self.surname = surname
self.position = position
self.media = media
def show(self):
print(self.name, self.surname, self.position, self.media.type, self.media.name)
media = Media('Журнал', '"Код"')
mike = Editorial('Михаил', 'Полянин', 'главный редактор', media)
mike.show()
Иногда полезно вынести часть инициализации в отдельные методы для упрощения конструктора и улучшения читаемости кода:
class Employee:
def __init__(self, name, surname, since): # Замена точки на запятую
self.name = name
self.surname = surname
self.set_since(since)
def set_since(self, since):
if not (2019 <= since <= 2024):
raise ValueError('Год начала работы должен быть в диапазоне 2019–2024')
self.since = since
ira = Employee('Ира', 'Михеева', 2019)
Если есть несколько вариантов инициализации, нужно использовать внутренние методы во избежание дублирования кода:
class MyClass:
def __init__(self, value, flag):
if flag:
self._initialize_with_flag(value)
else:
self._initialize_without_flag(value)
def _initialize_with_flag(self, value):
self.value = value * 2
def _initialize_without_flag(self, value):
self.value = value
Дополнительные методы инициализации
Иногда бывает полезно создавать объекты через классовые методы, особенно если требуется сложная логика создания объектов. Например, когда нужно предоставить несколько способов инициализации объектов или когда необходимо выполнить предварительную обработку данных перед созданием объекта. Классовый метод обозначается специальным декоратором classmethod
и, в отличие от обычных методов, первым аргументом принимает класс cls
вместо self
. Это позволяет работать с классом и его параметрами, а не с конкретным объектом:
# определяем класс и его параметры
class MyClass:
def __init__(self, value):
self.value = value
# применяем декоратор
@classmethod
def from_alternate_constructor(cls, alt_value):
# Какая-то сложная логика
value = alt_value * 2
return cls(value)
obj = MyClass.from_alternate_constructor(10)
В некоторых случаях полезно использовать метод __post_init__
, который вызывается после основного конструктора. Он используется вместе с декоратором @dataclass
и позволяет выполнять дополнительные действия после автоматической инициализации параметров, определённых в классе с @dataclass
.
# подключаем модуль dataclasses
from dataclasses import dataclass, field
# определяем класс и его параметры
@dataclass
class MyClass:
value: int
extra: int = field(init=False)
# добавляем метод __post_init__
def __post_init__(self):
self.extra = self.value * 2
Конструкторы и наследование
Наследование в Python позволяет создавать новый класс на основе существующего. Новый класс, называемый дочерним (или подклассом), наследует параметры и методы родительского класса.
Когда создаётся родительский класс, в нём часто определяется конструктор для инициализации параметров:
class Animal:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def make_sound(self):
pass
Если дочерний класс не переопределяет конструктор, он автоматически наследует конструктор родительского класса:
class Animal:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return 'Гав'
dog = Dog(name='Себастиан', breed='ротвейлер')
print(dog.name) # Себастиан
print(dog.breed) # ротвейлер
print(dog.make_sound()) # Гав!
Иногда нужно, чтобы дочерний класс имел свой собственный конструктор. В этом случае необходимо вызывать конструктор родительского класса с помощью функции super() для инициализации его части:
class Animal:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def make_sound(self):
pass
class Dog(Animal):
def __init__(self, name, breed, age):
# Вызов конструктора родительского класса
super().__init__(name, breed)
self.age = age
def make_sound(self):
return 'Woof!'
dog = Dog(name="Себастиан", breed="ротвейлер", age=5)
print(dog.name) # Себастиан
print(dog.breed) # ротвейлер
print(dog.age) # 5
Конструктор дочернего класса может принимать дополнительные аргументы, необходимые для инициализации новых параметров:
class Animal:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def make_sound(self):
pass
class Bird(Animal):
def __init__(self, name, breed, can_fly):
# Вызов конструктора родительского класса
super().__init__(name, breed)
self.can_fly = can_fly
def make_sound(self):
return "Чик-чирик!"
bird = Bird(name="Твити", breed="канарейка", can_fly=True)
print(bird.name) # Твити
print(bird.breed) # канарейка
print(bird.can_fly) # True
В Python поддерживается множественное наследование — это означает, что класс может наследовать от нескольких родительских классов. В этом случае необходимо аккуратно вызывать конструкторы всех родительских классов.
class A:
def __init__(self, a):
self.a = a
class B:
def __init__(self, b):
self.b = b
class C(A, B):
def __init__(self, a, b, c):
# Явный вызов конструктора класса A
A.__init__(self, a)
# Явный вызов конструктора класса B
B.__init__(self, b)
self.c = c
c = C(a=1, b=2, c=3)
print(c.a) # 1
print(c.b) # 2
print(c.c) # 3
Также есть альтернативный способ с использованием super()
:
class A:
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
class C(B):
def __init__(self, a, b, c):
super().__init__(a, b)
self.c = c
c = C(a=1, b=2, c=3)
print(c.a) # 1
print(c.b) # 2
print(c.c) # 3
При использовании множественного наследования важно учитывать порядок вызова конструкторов всех классов. В этом может помочь метод разрешения порядка MRO (Method Resolution Order), который можно проверить с помощью ClassName.mro()
.
Частые ошибки при использовании конструкторов
Перегрузка конструктора. Если определить несколько конструкторов, интерпретатор будет рассматривать только последний конструктор и выдаст ошибку, если последовательность параметров не совпадёт с последним конструктором:
class Employee:
# конструктор с одним параметром
def __init__(self, name):
self.name = name
# конструктор с двумя параметрами
def __init__(self, name, surname):
self.name = name
self.surname = surname
ira = Employee('Ира')
masha = Employee('Маша', 'Климентьева')
При выполнении такой код выдаст ошибку:
TypeError: Employee.__init__() missing 1 required positional argument: 'surname'
Отсутствие вызова super() в наследуемом классе. Когда класс наследуется от другого класса, инициализация родительского класса должна быть выполнена явно. Такой код выдаст ошибку:
class Parent:
def __init__(self, value):
self.value = value
class Child(Parent):
def __init__(self, value, extra):
# Ошибка: отсутствие super()
# self.value = value
super().__init__(value)
self.extra = extra
Неправильное использование аргументов. Если забыть про обязательные аргументы или неправильно их использовать в конструкторе, это может привести к ошибкам:
class MyClass:
def __init__(self, value1, value2):
self.value1 = value1
self.value2 = value2
# Ошибка: недостаточно аргументов
obj = MyClass(10) # TypeError
Изменение параметров по умолчанию, если они являются изменяемыми объектами. Если параметр по умолчанию является изменяемым объектом (например, список или словарь), это может привести к неожиданному поведению:
class MyClass:
def __init__(self, values=[]):
self.values = values
obj1 = MyClass()
obj2 = MyClass()
obj1.values.append(1)
# [1], а не []
print(obj2.values)
Вызов методов из конструктора. Если метод, вызываемый из конструктора, зависит от параметров, которые ещё не инициализированы, это может привести к ошибкам:
class Example:
def __init__(self, value):
self.process_value()
self.value = value
def process_value(self):
print(self.value)
example = Example(42)
Код выдаст ошибку:
AttributeError: 'Example' object has no attribute 'value'