Добавить в корзинуПозвонить
Найти в Дзене
Код как искусство

Магические методы Python: как превратить свой класс в настоящего джедая

Вы уже умеете создавать классы, наследовать их и использовать полиморфизм. Но знаете ли вы, что можно заставить ваш собственный объект вести себя как список? Или как число? Или чтобы его можно было вызвать как функцию? Всё это делают магические методы (их ещё называют dunder-методы — от double underscore). Это специальные имена вроде __init__, __str__, __add__, которые Python вызывает автоматически в определённых ситуациях. Сегодня мы разберём самые полезные магические методы и превратим скучный класс в интеллектуальный объект. Это методы, имя которых начинается и заканчивается двумя подчёркиваниями (__). Вы их уже использовали: __init__ вызывается при создании объекта. Но есть десятки других. Когда вы пишете print(obj), Python ищет у объекта метод __str__. Когда пишете len(obj) — ищет __len__. Когда obj1 + obj2 — ищет __add__. Это позволяет вашим классам органично вписываться в синтаксис языка. Вызывается при создании объекта. Знаком каждому. class Book:
def __init__(self, title,
Оглавление

Вы уже умеете создавать классы, наследовать их и использовать полиморфизм. Но знаете ли вы, что можно заставить ваш собственный объект вести себя как список? Или как число? Или чтобы его можно было вызвать как функцию? Всё это делают магические методы (их ещё называют dunder-методы — от double underscore). Это специальные имена вроде __init__, __str__, __add__, которые Python вызывает автоматически в определённых ситуациях.

Сегодня мы разберём самые полезные магические методы и превратим скучный класс в интеллектуальный объект.

1. Что такое магические методы?

Это методы, имя которых начинается и заканчивается двумя подчёркиваниями (__). Вы их уже использовали: __init__ вызывается при создании объекта. Но есть десятки других.

Когда вы пишете print(obj), Python ищет у объекта метод __str__. Когда пишете len(obj) — ищет __len__. Когда obj1 + obj2 — ищет __add__. Это позволяет вашим классам органично вписываться в синтаксис языка.

2. Самые важные магические методы

__init__(self, ...) — конструктор

Вызывается при создании объекта. Знаком каждому.

__str__(self) и __repr__(self) — текстовое представление

  • __str__ — для пользователя (неформальное, красивое). Используется в print() и str().
  • __repr__ — для разработчика (однозначное, желательно чтобы можно было восстановить объект). Используется в отладке, в консоли.

class Book:
def __init__(self, title, author):
self.title = title
self.author = author

def __str__(self):
return f"{self.title} — {self.author}"

def __repr__(self):
return f"Book('{self.title}', '{self.author}')"

b = Book("1984", "Оруэлл")
print(b) # 1984 — Оруэлл (__str__)
print(repr(b)) # Book('1984', 'Оруэлл') (__repr__)

__len__(self) — длина

class Library:
def __init__(self, books):
self.books = books
def __len__(self):
return len(self.books)

lib = Library(["Book1", "Book2"])
print(len(lib)) # 2

__getitem__(self, key) и __setitem__ — доступ по индексу или ключу

Делает объект похожим на список или словарь.

class ShoppingCart:
def __init__(self):
self.items = []
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, index, value):
self.items[index] = value
def add(self, item):
self.items.append(item)

cart = ShoppingCart()
cart.add("яблоко")
cart.add("банан")
print(cart[0]) # яблоко
cart[1] = "груша"
print(cart[1]) # груша

__call__(self, ...) — объект как функция

class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor

double = Multiplier(2)
print(double(5)) # 10 (вызываем объект как функцию)

__add__, __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"({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)

__eq__, __lt__, __gt__ — сравнение

class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.age == other.age
def __lt__(self, other):
return self.age < other.age

alice = Person("Алиса", 30)
bob = Person("Боб", 25)
print(alice == bob) # False
print(bob < alice) # True

3. Полезные менее очевидные методы

Метод Что делает

__contains__(self, item) для item in obj

__iter__(self) делает объект итерируемым (для for x in obj)

__next__(self) для итераторов

__enter__ и __exit__ для контекстного менеджера (with ... as)

__del__(self) деструктор (вызывается при удалении объекта)

4. Типичные ошибки новичков

❌ Забыли вернуть что-то в __add__

Метод должен возвращать новый объект, а не None.

❌ Путают __str__ и __repr__

Если определён только __repr__, то print(obj) использует его как запасной. Но лучше определять оба.

❌ __len__ возвращает не целое число

Должен возвращать int, иначе ошибка.

❌ Слишком много магии

Не стоит переопределять __getitem__, если объект не является по сути коллекцией. Код должен быть предсказуемым.

5. Живой пример: класс "Дробь" с арифметикой

Создадим класс обыкновенной дроби, который умеет складываться, вычитаться, сравниваться и красиво печататься:

import math

class Fraction:
def __init__(self, numerator, denominator):
if denominator == 0:
raise ValueError("Знаменатель не может быть нулём")
self.num = numerator
self.den = denominator
self._reduce()

def _reduce(self):
gcd = math.gcd(self.num, self.den)
self.num //= gcd
self.den //= gcd

def __add__(self, other):
new_num = self.num * other.den + other.num * self.den
new_den = self.den * other.den
return Fraction(new_num, new_den)

def __sub__(self, other):
new_num = self.num * other.den - other.num * self.den
new_den = self.den * other.den
return Fraction(new_num, new_den)

def __mul__(self, other):
return Fraction(self.num * other.num, self.den * other.den)

def __truediv__(self, other):
return Fraction(self.num * other.den, self.den * other.num)

def __eq__(self, other):
return self.num == other.num and self.den == other.den

def __str__(self):
return f"{self.num}/{self.den}"

def __repr__(self):
return f"Fraction({self.num}, {self.den})"

# Использование
a = Fraction(1, 2)
b = Fraction(1, 3)
print(a + b) # 5/6
print(a - b) # 1/6
print(a * b) # 1/6
print(a / b) # 3/2
print(a == Fraction(2, 4)) # True (сократится до 1/2)

Всего несколько методов — и ваши дроби ведут себя как встроенные числа. Магия, да?

Заключение

Магические методы позволяют вашим классам говорить на языке Python. Вы узнали:

  • как управлять строковым представлением (__str__, __repr__),
  • как сделать объект контейнером (__len__, __getitem__),
  • как добавить арифметику (__add__, __sub__ и другие),
  • как сделать объект вызываемым (__call__).

Теперь ваш код может быть не только правильным, но и красивым — словно он всегда был частью языка.

Следующая статья будет про итераторы и генераторы — как создавать свои последовательности и эффективно работать с данными. А пока — попробуйте добавить магические методы в один из ваших старых классов. Результат вас удивит.

Пишите в комментариях, какой магический метод показался самым неожиданным и где вы его применили.

Статья подготовлена для канала «Код как искусство». Подписывайтесь, чтобы писать код, который удивляет своей естественностью.