__init__ в Python — это специальный метод класса, который автоматически вызывается сразу после создания объекта и заполняет его начальными данными (атрибутами). В разговоре его называют конструктором, но строго говоря это инициализатор: сам объект к моменту вызова __init__ уже создан методом __new__, а __init__ только настраивает его и ничего не возвращает.
Содержание
- Три понятия, которые путают
- Минимальный рабочий пример
- Что такое self
- Параметры по умолчанию и ловушка со списком
__init__ничего не возвращает- Несколько конструкторов: перегрузки нет
- Наследование:
super().__init__ __new__и__init__: когда нужен__new__- Когда
__init__можно не писать: dataclass - Если не получилось: симптом -> причина
- Выводы
- Где применяется / связь с практикой
- FAQ
Ниже — как написать __init__, зачем нужен self, как задавать параметры по умолчанию и не попасть в ловушку с изменяемым значением, как вызывать super().__init__ при наследовании и когда вообще нужен __new__. Все примеры прогнаны на Python 3.14.7 (24.09.2026).
Три понятия, которые путают
| Понятие | Что делает | Когда вызывается | Что возвращает |
|---|---|---|---|
Вызов класса Point(1, 2) |
запускает создание объекта целиком | когда вы его пишете | готовый объект |
__new__(cls, ...) |
создает пустой объект (выделяет его) | первым | новый объект |
__init__(self, ...) |
заполняет уже созданный объект | вторым, если __new__ вернул экземпляр этого класса |
только None |
Упрощенная модель: вызов класса = __new__, затем __init__. Точнее этот порядок задает метакласс type, и у модели есть граница: если __new__ вернет объект другого класса, __init__ не вызовется (подробнее ниже). В обычном прикладном коде достаточно написать только __init__.
Минимальный рабочий пример
class BankAccount:
bank_name = "Otus Bank" # атрибут класса: общий для всех объектов
def __init__(self, owner, balance=0):
if balance < 0:
raise ValueError("balance не может быть отрицательным")
self.owner = owner # атрибуты экземпляра: у каждого свои
self.balance = balance
def deposit(self, amount):
self.balance += amount
a = BankAccount("Анна", 100)
b = BankAccount("Борис") # balance возьмет значение по умолчанию
a.deposit(50)
print(a.owner, a.balance)
print(b.owner, b.balance)
print(a.bank_name, b.bank_name)
В консоли появятся три строки:
Анна 150
Борис 0
Otus Bank Otus Bank
Разбор по строкам:
def __init__(self, owner, balance=0)— два подчеркивания с каждой стороны обязательны. Написали_init_или__int__— Python не узнает метод, и аргументы при создании объекта упадут сTypeError.self.owner = ownerсоздает атрибут экземпляра: уaиbон разный.bank_nameобъявлен в теле класса, а не в__init__, поэтому это атрибут класса, общий для всех объектов.- Проверка
if balance < 0— типичная работа__init__: не пустить в программу объект в неверном состоянии. Ошибку лучше поднять сразу, чем потом искать, откуда взялся отрицательный баланс.
Что такое self
self — первый параметр __init__ и любого обычного метода. В него Python сам передает объект, который сейчас инициализируется. Имя self — соглашение, а не ключевое слово, но другое имя сбивает читателей кода и линтеры.
class Point:
def __init__(self, x, y):
print("id(self) =", id(self))
self.x = x
self.y = y
p = Point(1, 2)
print("id(p) =", id(p))
print(p.x, p.y)
try:
Point(1)
except TypeError as e:
print(e)
id(self) = 277188087235984
id(p) = 277188087235984
1 2
Point.__init__() missing 1 required positional argument: 'y'
Числа id у вас будут другими, но совпадут между собой: self внутри __init__ и p снаружи — один и тот же объект. Последняя строка показывает, что параметры __init__ — это параметры вызова класса: не передали y — получили TypeError с именем недостающего аргумента.
Параметры по умолчанию и ловушка со списком
Значение по умолчанию вычисляется один раз — при определении функции, а не при каждом вызове. Для чисел и строк это незаметно, а изменяемый объект (список, словарь) окажется общим для всех экземпляров.
Неверно:
class Cart:
def __init__(self, items=[]):
self.items = items
c1 = Cart()
c2 = Cart()
c1.items.append("книга")
print(c2.items)
print(c1.items is c2.items)
Фактический результат — товар «появился» во второй корзине:
['книга']
True
Исправление — None как маркер и новый список внутри __init__:
class Cart:
def __init__(self, items=None):
self.items = [] if items is None else list(items)
c1 = Cart()
c2 = Cart()
c1.items.append("книга")
print(c2.items)
print(c1.items is c2.items)
[]
False
list(items) заодно делает копию переданного списка, чтобы корзина не зависела от внешней переменной. Нужна ли копия — решение по задаче; если объект должен разделять данные с вызывающим кодом, копировать не надо.
__init__ ничего не возвращает
Метод обязан вернуть None (просто не писать return со значением). Попытка вернуть что-то другое падает при создании объекта:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
return self
try:
Temperature(20)
except TypeError as e:
print(type(e).__name__, e)
TypeError __init__() should return None, not 'Temperature'
Исправление — удалить return self: объект вернет сам вызов Temperature(20).
Несколько конструкторов: перегрузки нет
В отличие от Java или C#, в Python нельзя объявить два __init__ с разными параметрами. Второе определение просто заменяет первое:
class User:
def __init__(self, name):
self.name = name
def __init__(self, name, age):
self.name = name
self.age = age
try:
User("Анна")
except TypeError as e:
print(e)
User.__init__() missing 1 required positional argument: 'age'
Вместо перегрузки используют параметры по умолчанию или альтернативные конструкторы через @classmethod. Они разбирают вход и вызывают основной __init__ через cls(...), поэтому проверки остаются в одном месте и корректно работают у наследников:
from datetime import date
class Person:
def __init__(self, name, birth_year):
self.name = name
self.birth_year = birth_year
@classmethod
def from_string(cls, text):
name, year = text.split(",")
return cls(name.strip(), int(year))
@classmethod
def from_age(cls, name, age, today=None):
today = today or date(2026, 9, 24)
return cls(name, today.year - age)
p1 = Person("Анна", 1998)
p2 = Person.from_string("Борис, 2001")
p3 = Person.from_age("Вера", 30)
for p in (p1, p2, p3):
print(p.name, p.birth_year)
Анна 1998
Борис 2001
Вера 1996
Дата в from_age зафиксирована, чтобы вывод воспроизводился; в реальном коде по умолчанию берут date.today(). Так устроены и встроенные примеры вроде dict.fromkeys и datetime.fromisoformat.
Наследование: super().__init__
Если у дочернего класса нет своего __init__, Python возьмет родительский. Если есть — родительский не вызывается автоматически, и атрибуты родителя не появятся:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Manager(Employee):
def __init__(self, name, salary, team):
self.team = team # super().__init__ забыли
m = Manager("Анна", 300000, ["Борис"])
print(m.team)
try:
print(m.name)
except AttributeError as e:
print(e)
['Борис']
'Manager' object has no attribute 'name'
Ошибка всплывает не при создании объекта, а позже, при первом обращении к атрибуту, поэтому ее легко пропустить. Исправление — явно вызвать инициализатор родителя:
class Employee:
def __init__(self, name, salary):
print("Employee.__init__")
self.name = name
self.salary = salary
class Manager(Employee):
def __init__(self, name, salary, team):
super().__init__(name, salary)
print("Manager.__init__")
self.team = team
class Intern(Employee):
pass # своего __init__ нет
m = Manager("Анна", 300000, ["Борис"])
print(m.name, m.salary, m.team)
i = Intern("Вера", 80000)
print(i.name)
Employee.__init__
Manager.__init__
Анна 300000 ['Борис']
Employee.__init__
Вера
Порядок решаете вы: super().__init__ обычно ставят первой строкой, чтобы код ниже мог опираться на атрибуты родителя. super() лучше, чем Employee.__init__(self, ...): при множественном наследовании он идет по MRO (порядку разрешения методов) и не вызывает общий базовый класс дважды — при условии, что все классы иерархии тоже вызывают super().__init__, а не родителя по имени.
__new__ и __init__: когда нужен __new__
Порядок вызова легко увидеть:
class Demo:
def __new__(cls, *args, **kwargs):
print("1. __new__: создаю объект", cls.__name__)
instance = super().__new__(cls)
return instance
def __init__(self, value):
print("2. __init__: заполняю объект, value =", value)
self.value = value
d = Demo(42)
print("3. готово:", d.value)
1. __new__: создаю объект Demo
2. __init__: заполняю объект, value = 42
3. готово: 42
Переопределять __new__ нужно редко. Главный практический случай — наследование от неизменяемых типов (int, float, str, tuple): их значение задается при создании, и в __init__ его уже не поменять.
class Celsius(float):
def __new__(cls, value):
if value < -273.15:
raise ValueError("ниже абсолютного нуля")
return super().__new__(cls, round(value, 1))
t = Celsius(36.649)
print(t, isinstance(t, float))
class Broken(float):
def __init__(self, value):
self = round(value, 1) # переприсваивание локального имени
print(Broken(36.649))
36.6 True
36.649
В Broken округление не сработало: self = ... меняет только локальную переменную, а число уже создано float.__new__.
Граница, о которой говорилось в таблице: если __new__ вернул не экземпляр своего класса (например, строку), __init__ пропускается.
Singleton через __new__: __init__ вызывается каждый раз
Частый учебный пример — «одиночка», у которого __new__ возвращает уже созданный объект. Подвох: раз объект — экземпляр класса, __init__ отрабатывает при каждом вызове и перезаписывает состояние.
class Config:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, env):
print("__init__ вызван, env =", env)
self.env = env
a = Config("prod")
b = Config("test")
print(a is b, a.env)
__init__ вызван, env = prod
__init__ вызван, env = test
True test
Настройка prod тихо превратилась в test. Если одиночка действительно нужен, в Python чаще хватает объекта на уровне модуля; иначе в __init__ нужен флаг «уже инициализирован». Этот вариант не потокобезопасен: при одновременном первом вызове из двух потоков нужна блокировка.
Когда __init__ можно не писать: dataclass
Для классов, которые в основном хранят данные, модуль dataclasses генерирует __init__ сам по аннотациям полей. Дополнительную проверку кладут в __post_init__, изменяемые значения по умолчанию — через field(default_factory=...) (это решение той же ловушки со списком):
from dataclasses import dataclass, field
@dataclass
class Order:
order_id: int
items: list[str] = field(default_factory=list)
total: float = 0.0
def __post_init__(self):
if self.total < 0:
raise ValueError("total < 0")
o = Order(1, ["книга"], 990.0)
print(o)
print(Order(2))
Order(order_id=1, items=['книга'], total=990.0)
Order(order_id=2, items=[], total=0.0)
Аннотации в dataclass не проверяют типы при выполнении: Order("1") создастся без ошибки. Для проверки типов нужен статический анализатор (mypy, pyright) или явная проверка в __post_init__.
Если не получилось: симптом -> причина
| Симптом | Вероятная причина | Что сделать |
|---|---|---|
TypeError: Point() takes no arguments |
метод назван с ошибкой (_init_, __int__) и не определен |
проверить имя: два подчеркивания с обеих сторон |
missing 1 required positional argument |
при создании не передан аргумент __init__ |
передать его или задать значение по умолчанию |
P.__init__() takes 1 positional argument but 2 were given |
в определении забыли self |
первым параметром поставить self |
'X' object has no attribute ... у наследника |
не вызван super().__init__ |
добавить вызов в начало __init__ |
should return None |
в __init__ есть return значение |
убрать возврат |
| данные «перетекают» между объектами | изменяемое значение по умолчанию | None + новый список внутри |
Выводы
__init__— инициализатор: заполняет уже созданный объект атрибутами и возвращает толькоNone; создает объект__new__.self— это сам создаваемый объект, Python передает его первым аргументом автоматически.- Изменяемые значения по умолчанию (
[],{}) в параметрах__init__общие для всех объектов — используйтеNoneилиdefault_factory. - Перегрузки
__init__нет: вместо нее параметры по умолчанию и альтернативные конструкторы через@classmethod. - В наследнике со своим
__init__родительский нужно вызвать явно черезsuper().__init__;__new__переопределяют в редких случаях, например для подклассовint/str/float.
Где применяется / связь с практикой
Инициализация объектов — основа любого кода на классах: модели в Django и SQLAlchemy, dataclass-конфиги, клиенты API, обработчики в тестах. Ошибки с super().__init__ и изменяемыми значениями по умолчанию регулярно встречаются на ревью и собеседованиях джунов.
Освойте тему на практике
Если хочется разобрать классы, наследование и работу с объектами на практических задачах с проверкой кода, посмотрите курс «Python-разработчик. Базовый уровень». Бесплатно познакомиться с преподавателями и форматом можно на открытых уроках Otus.
FAQ
Можно ли вызвать __init__ повторно на готовом объекте?
Технически да (obj.__init__(...)), и атрибуты перезапишутся, но это читается как ошибка. Для сброса состояния лучше завести отдельный метод, например reset().
Есть ли в Python деструктор?
Есть метод __del__, но момент его вызова не гарантирован и зависит от сборщика мусора. Для освобождения файлов и соединений используют with и контекстные менеджеры.
Где объявлять атрибуты: в __init__ или в теле класса?
Данные конкретного объекта — в __init__ через self. В теле класса — то, что общее для всех экземпляров (константы, счетчики); изменяемый список в теле класса станет общим, как и в параметре по умолчанию.



