Услуги Портфолио Контакты БлогНовости +7 (800) 511‑32‑27 Обсудить проект
Статьи

Что такое магические методы в Python

14 декабря 2022·Обновлено 30 сентября 2026·12 мин чтения
Логотип YuSMP Group
Автор материалаРедакция компании YuSMP GroupБлог YuSMP Group
Профиль редакции
Что такое магические методы в Python

Магические методы 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)
Перегрузка арифметических операторов в Python: символы плюс, минус, умножить и разделить поверх кода

__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
Итераторы и последовательности в Python: связанные блоки данных с циклом перебора

На практике полный протокол итератора (__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 подойдёт для вашего продукта.

Нужны разработчики в команду?

Подберём инженеров нужного стека под ваш проект — быстрый старт.

Обсудить команду