Магические методы Python (dunder-методы, от «double underscore» — двойное подчёркивание) — специальные методы класса, имя которых начинается и заканчивается двойным подчёркиванием: __add__, __str__, __init__. Python вызывает их автоматически в ответ на определённые операции — вам не нужно обращаться к ним напрямую.
Именно дандер-методы делают классы «питоничными»: позволяют перегружать операторы, делать объекты итерируемыми, вызываемыми, работать с with и len(). В команде backend-разработки на Python в YuSMP Group дандер-методы — обязательная часть чистого ООП-кода: без них не построить читаемый ORM или собственную структуру данных.
TL;DR — ключевые тезисы
- Магические методы — дандеры вида
__init__,__add__,__str__— Python вызывает их автоматически на определённые операции, а не по прямому вызову. __add__перегружает+, но у него есть менее известные пары —__radd__(отражённый оператор) и__iadd__(составное присваивание+=).__str__— для пользователя,__repr__— для разработчика и отладчика; путать их — частая ошибка.__getitem__,__iter__и__next__делают объект индексируемым и итерируемым — без них не работаетfor x in obj.- Полный список методов — в таблице ниже, разбор с кодом — по каждому разделу статьи.
Справочная таблица: магические методы Python по категориям
| Категория | Метод | Когда вызывается |
|---|---|---|
| Инициализация | __init__(self, ...) | После создания объекта |
| Инициализация | __new__(cls, ...) | До __init__, создаёт экземпляр |
| Удаление | __del__(self) | При уничтожении объекта GC |
| Строки | __str__(self) | str(obj), print(obj) |
| Строки | __repr__(self) | repr(obj), отладчик |
| Арифметика | __add__(self, other) | a + b |
| Арифметика | __sub__(self, other) | a - b |
| Арифметика | __mul__(self, other) | a * b |
| Арифметика | __truediv__(self, other) | a / b |
| Арифметика | __mod__(self, other) | a % b |
| Арифметика | __pow__(self, other) | a ** b |
| Отражённые | __radd__(self, other) | other + a, если у other нет __add__ |
| Составные | __iadd__(self, other) | a += b |
| Унарные | __neg__(self) | -a |
| Унарные | __abs__(self) | abs(a) |
| Сравнение | __eq__(self, other) | a == b |
| Сравнение | __ne__(self, other) | a != b |
| Сравнение | __lt__(self, other) | a < b |
| Сравнение | __le__(self, other) | a <= b |
| Сравнение | __gt__(self, other) | a > b |
| Сравнение | __ge__(self, other) | a >= b |
| Контейнеры | __len__(self) | len(obj) |
| Контейнеры | __getitem__(self, key) | obj[key] |
| Контейнеры | __setitem__(self, key, v) | obj[key] = v |
| Контейнеры | __contains__(self, item) | x in obj |
| Контейнеры | __iter__(self) | for x in obj, iter(obj) |
| Контейнеры | __next__(self) | Следующий элемент итератора, next(obj) |
| Вызов | __call__(self, ...) | obj(...) |
| Атрибуты | __getattr__(self, name) | Доступ к несуществующему атрибуту |
| Атрибуты | __setattr__(self, name, v) | Присваивание атрибута |
| Менеджер контекста | __enter__(self) | Вход в блок with |
| Менеджер контекста | __exit__(self, ...) | Выход из блока with |
Разбор ключевых методов с примерами кода
__add__ — перегрузка оператора +
__add__ вызывается, когда Python встречает оператор + между двумя объектами. Без переопределения складывать пользовательские объекты нельзя. Чтобы перегрузить другие операторы, используйте соответствующие методы: __sub__ для -, __mul__ для *, __truediv__ для /.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f'Vector({self.x}, {self.y})'
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)

__radd__ и __iadd__ — отражённые и составные операторы
__add__ — не единственный метод, отвечающий за сложение. У Python есть ещё два родственных дандера, о которых часто забывают:
__radd__(self, other)— «отражённый» (reflected) вариант. Вызывается, когда левый операнд не умеет складываться с правым, то есть его__add__вернулNotImplementedили отсутствует вовсе. Классический пример —5 + vector: уintнет метода для сложения сVector, и Python обращается кvector.__radd__(5).__iadd__(self, other)— составное присваиваниеa += b. Если метод не определён, Python откатывается наa = a + b(то есть на обычный__add__), создавая новый объект. Определив__iadd__явно, можно изменить объект на месте — это быстрее для изменяемых коллекций вроде собственных списков или матриц.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __radd__(self, other):
# other = 0 — поддержка sum([v1, v2, v3])
if other == 0:
return self
return self.__add__(other)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self # изменяем объект на месте, не создаём новый
def __repr__(self):
return f'Vector({self.x}, {self.y})'
v1, v2 = Vector(1, 2), Vector(3, 4)
print(sum([v1, v2])) # Vector(4, 6) — работает благодаря __radd__
v1 += v2
print(v1) # Vector(4, 6) — тот же объект, изменённый на месте
Похожая пара есть у каждого арифметического оператора: __rsub__/__isub__, __rmul__/__imul__ и так далее — сигнатура и логика вызова идентичны.
__neg__ и __abs__ — унарные операторы
Унарные операторы работают с одним операндом, а не с двумя, — их методы принимают только self. Самые частые: __neg__ для -a, __pos__ для +a, __abs__ для abs(a) и __invert__ для побитового ~a.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __neg__(self):
return Vector(-self.x, -self.y)
def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __repr__(self):
return f'Vector({self.x}, {self.y})'
v = Vector(3, -4)
print(-v) # Vector(-3, 4)
print(abs(v)) # 5.0 — длина вектора
__new__ — создание экземпляра
В Java и C# объект создаётся оператором new явно. В Python __new__() вызывается неявно — до __init__(): сначала создаётся экземпляр, затем инициализируется.
class Employee:
def __new__(cls):
print("__new__ вызван — создаётся экземпляр")
return super().__new__(cls)
def __init__(self):
print("__init__ вызван — объект инициализируется")
self.name = 'Дмитрий'
emp = Employee()
# __new__ вызван — создаётся экземпляр
# __init__ вызван — объект инициализируется
Переопределять __new__ нужно редко — только при создании подклассов неизменяемых типов (int, str) или реализации паттерна Singleton.
__str__ и __repr__ — строковое представление
__str__ возвращает читаемое представление объекта для пользователя — вызывается через str() и print(). __repr__ — техническое представление для разработчика, используется в отладчике. Правило: вывод __repr__ должен позволять воссоздать объект.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def __str__(self):
return f'{self.name}, зарплата: {self.salary} ₽'
def __repr__(self):
return f"Employee(name='{self.name}', salary={self.salary})"
emp = Employee('Дмитрий', 120000)
print(str(emp)) # Дмитрий, зарплата: 120000 ₽
print(repr(emp)) # Employee(name='Дмитрий', salary=120000)
__ge__ и __lt__ — операторы сравнения
Методы сравнения позволяют сопоставлять пользовательские объекты стандартными операторами. Каждый оператор соответствует своему методу: < → __lt__, <= → __le__, >= → __ge__, > → __gt__, == → __eq__.
class Distance:
def __init__(self, meters):
self.meters = meters
def __lt__(self, other):
return self.meters < other.meters
def __ge__(self, other):
return self.meters >= other.meters
d1 = Distance(100)
d2 = Distance(200)
print(d1 < d2) # True
print(d1 >= d2) # False
Совет: используйте декоратор @functools.total_ordering — достаточно определить __eq__ и один метод сравнения, Python сгенерирует остальные автоматически.
__call__ — вызываемый объект
__call__ делает экземпляр класса вызываемым как обычную функцию. Полезен при реализации замыканий, декораторов, стейтфул-функторов.
class Person:
def __call__(self, other):
return f'Привет, {other}!'
alice = Person()
print(alice('Боб')) # Привет, Боб!
__enter__ и __exit__ — менеджер контекста
Python вызывает __enter__() при входе в блок with и __exit__() — при выходе (в том числе при исключении). Объект, реализующий оба метода, называется менеджером контекста — это стандартный способ управлять ресурсами: соединениями, файлами, транзакциями.
class DatabaseConnection:
def __init__(self, url):
self.url = url
def __enter__(self):
print(f'Подключение к {self.url}')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f'Закрытие соединения с {self.url}')
return False # не подавляем исключения
with DatabaseConnection('db://localhost') as conn:
print('Работаем с базой данных')
# Подключение к db://localhost
# Работаем с базой данных
# Закрытие соединения с db://localhost
Если __exit__ возвращает True, исключение внутри блока with подавляется. False или None — исключение всплывает дальше.
__getitem__, __iter__ и __next__ — своя последовательность и итератор
Чтобы объект работал в цикле for x in obj, поддерживал obj[i] и оператор in, ему не нужно наследоваться от list или dict — достаточно реализовать нужные дандеры. Есть два независимых пути:
__getitem__(self, key)— самый простой способ сделать объект и индексируемым, и перебираемым: если__iter__не определён, Python попробует обращаться по индексам0, 1, 2…, пока не поймаетIndexError.__iter__(self)+__next__(self)— «настоящий» итератор.__iter__возвращает объект с методом__next__(частоself), а__next__отдаёт следующий элемент и поднимаетStopIteration, когда элементы закончились.
class Range3:
"""Упрощённый аналог range() с собственным протоколом итерации."""
def __init__(self, start, stop):
self.start, self.stop = start, stop
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += 1
return value
def __getitem__(self, index):
return self.start + index
def __len__(self):
return self.stop - self.start
r = Range3(1, 4)
for x in r:
print(x) # 1, 2, 3
print(r[0], len(r)) # 1 3

На практике полный протокол итератора (__iter__ + __next__) нужен для «ленивых» последовательностей — когда элементы вычисляются по одному и не хранятся в памяти целиком. Для простых обёрток над готовой коллекцией обычно достаточно __getitem__ и __len__.
Типичные ошибки при работе с дандер-методами
Магические методы легко использовать неправильно — Python не выбросит исключение на этапе разработки, ошибка проявится позже как трудноуловимый баг.
- Переопределили
__eq__и забыли про__hash__. Объект молча становится нехешируемым — попытка добавить его вsetили использовать как ключdictупадёт сTypeError: unhashable type. - Спутали
__str__и__repr__. Если определить только__repr__, Python использует его и дляstr()— это нормально. Обратная ситуация (только__str__) приводит к тому, что в отладчике и логах объект выводится нечитаемым техническим представлением по умолчанию. - Реализовали
__add__, но не подумали про__radd__. Код видаa + bсработает, аsum([a, b])упадёт —sum()начинает с0 + a, и без__radd__Python не знает, как сложитьintс вашим объектом. - Забыли
StopIterationв__next__. Без него циклforзависнет в бесконечном переборе — итератор обязан явно сигнализировать об окончании последовательности. - Тяжёлая логика внутри
__del__. Момент вызова__del__не гарантирован (зависит от сборщика мусора) — не полагайтесь на него для критичных операций вроде закрытия файлов или соединений с БД; для этого используйте__enter__/__exit__.
Часто задаваемые вопросы о магических методах Python
- Что такое магические методы Python?
- Это специальные методы класса с двойным подчёркиванием в имени —
__init__,__str__,__add__. Python вызывает их автоматически при определённых операциях: создании объекта, сложении, преобразовании в строку. Разработчик переопределяет их, чтобы задать кастомное поведение. - В чём разница между __str__ и __repr__?
__str__— читаемое представление для пользователя, используется вprint()иstr().__repr__— техническое представление для разработчика: отладчик, интерактивная консоль. Правило: вывод__repr__должен позволять воссоздать объект черезeval(repr(obj)).- Когда использовать __new__ вместо __init__?
__new__нужен редко: создание подклассов неизменяемых типов (int,str), реализация Singleton, управление пулом объектов. В большинстве задач достаточно__init__— он вызывается после__new__и инициализирует уже созданный экземпляр.- Зачем переопределять __eq__?
- По умолчанию
==сравнивает объекты по идентичности (адресу в памяти). Переопределив__eq__, задаёте сравнение по значению. Важно: без__hash__объект станет нехэшируемым и его нельзя добавить вsetили использовать как ключdict. - Как посмотреть все dunder-методы объекта?
- Вызовите
dir(obj)— он вернёт список всех атрибутов и методов включая магические. Фильтр только дандер-методов:[m for m in dir(obj) if m.startswith('__') and m.endswith('__')]. - Чем __radd__ отличается от __add__?
__add__(self, other)вызывается, когда объект — левый операнд (a + b→a.__add__(b)).__radd__(self, other)— «отражённый» вариант, срабатывающий, когда левый операнд не умеет складываться с вашим объектом, например5 + a, если уintнет подходящего__add__. Без__radd__такие выражения иsum()по списку объектов упадут с ошибкой.- Почему объект стал нехешируемым после переопределения __eq__?
- Python требует, чтобы равные по
__eq__объекты имели одинаковый хеш. Гарантировать это автоматически нельзя, поэтому при переопределении__eq__Python отключает унаследованный__hash__. Чтобы вернуть объект вset/dict-ключи, определите__hash__явно — обычно какhash()от тех же полей, что участвуют в сравнении.
__hash__ и __bool__ — хешируемость и логическое приведение
__hash__(self) возвращает целое число — хеш объекта, по которому Python быстро ищет элемент в set и ключ в dict. По умолчанию класс хешируется по id() объекта (адресу в памяти). Как только вы переопределяете __eq__, Python автоматически делает объект нехешируемым — Python-документация требует, чтобы равные по __eq__ объекты имели одинаковый хеш, а гарантировать это без явного __hash__ нельзя.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y)) # хешируем по тем же полям, что и __eq__
points = {Point(1, 2), Point(1, 2)}
print(len(points)) # 1 — объекты равны и хешируются одинаково
__bool__(self) определяет, что вернёт bool(obj) и как объект поведёт себя в условии if obj:. Без него Python использует __len__ — объект с нулевой длиной считается False. Если не определены ни __bool__, ни __len__, объект всегда истинен.
class ShoppingCart:
def __init__(self, items):
self.items = items
def __bool__(self):
return len(self.items) > 0
cart = ShoppingCart([])
if not cart:
print('Корзина пуста') # выведется
Итоги
Магические методы — один из ключевых механизмов Python, позволяющий создавать выразительные и идиоматичные классы. Понимание дандер-методов необходимо backend-разработчику: без них не обойтись при создании ORM, фреймворков и кастомных структур данных. Официальную спецификацию всех дандер-методов и их сигнатур можно свериться в Data Model — Python documentation.
Python входит в технологический стек backend-разработки в YuSMP Group. Посмотрите примеры наших проектов или свяжитесь через форму контактов — расскажем, как Python подойдёт для вашего продукта.
Нужны разработчики в команду?
Подберём инженеров нужного стека под ваш проект — быстрый старт.




