Ответ
В Python я активно использую магические методы (dunder methods) для создания интуитивных и Pythonic API. Вот основные категории:
1. Конструкторы и деструкторы:
class DatabaseConnection:
def __init__(self, connection_string):
self.conn = create_connection(connection_string)
def __del__(self):
if hasattr(self, 'conn'):
self.conn.close() # Гарантируем закрытие соединения
2. Строковое представление:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})" # Для пользователя
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})" # Для разработчика
3. Контейнеры и последовательности:
class CustomList:
def __init__(self, items):
self._items = list(items)
def __len__(self):
return len(self._items)
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, value):
self._items[index] = value
def __contains__(self, item):
return item in self._items
4. Операторы:
class Money:
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def __add__(self, other):
if self.currency != other.currency:
raise ValueError("Валюты должны совпадать")
return Money(self.amount + other.amount, self.currency)
def __eq__(self, other):
return (self.amount == other.amount and
self.currency == other.currency)
5. Контекстные менеджеры:
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.time() - self.start
print(f"Время выполнения: {self.elapsed:.2f} сек")
# Использование:
with Timer() as timer:
# выполнение кода
6. Вызываемые объекты:
class Polynomial:
def __init__(self, coefficients):
self.coeffs = coefficients
def __call__(self, x):
return sum(c * (x ** i) for i, c in enumerate(self.coeffs))
poly = Polynomial([1, 2, 3]) # 1 + 2x + 3x²
result = poly(2) # 1 + 4 + 12 = 17
В реальных проектах я чаще всего реализую __init__, __str__, __repr__, __eq__ и методы для контейнеров. Особенно полезны __enter__/__exit__ для работы с ресурсами и __call__ для создания функциональных объектов.
Ответ 18+ 🔞
Да ты посмотри, какая тема интересная подъехала! Эти ваши магические методы в Python, ёпта. Не то чтобы прям магия, конечно, но когда начинаешь их юзать — код сразу как будто оживает, становится понятным и для тебя, и для других. Прям доверия ебать ноль к классу, который не умеет красиво печататься или складываться.
Ну, смотри, я тебе по полочкам разложу, как я это вижу.
1. Конструкторы и деструкторы:
Вот смотри, создаёшь ты какой-нибудь класс для подключения к базе. Ну, __init__ — это святое, там всё понятно, инициализируешь. А вот __del__ — это уже хитрая жопа. Он вызывается, когда сборщик мусора приходит за твоим объектом. Но надеяться на него — это как играть в русскую рулетку. Может вызваться, а может и нет, когда ему вздумается. Поэтому лучше явно закрывать соединения, а не уповать на этот метод.
class DatabaseConnection:
def __init__(self, connection_string):
self.conn = create_connection(connection_string) # Всё, подключились
def __del__(self):
if hasattr(self, 'conn'):
self.conn.close() # Ну, на всякий случай, если забыли закрыть
2. Строковое представление:
Вот это, бля, основа основ! __str__ — это для красоты, для пользователя. А __repr__ — это для тебя, разработчика, чтобы в дебаггере или REPL сразу видно было, что за зверь. Разница — пиздец какая важная.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})" # Красиво и коротко
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})" # Точная копия, чтобы можно было скопировать и создать такой же
3. Контейнеры и последовательности:
Хочешь, чтобы твой объект вёл себя как список или словарь? Без проблем! Реализуй эти методы, и вот тебе — кастомный контейнер. len(), индексация через квадратные скобки, проверка in — всё твоё.
class CustomList:
def __init__(self, items):
self._items = list(items) # Спрятали внутрь
def __len__(self):
return len(self._items) # Теперь len(obj) работает
def __getitem__(self, index):
return self._items[index] # Теперь obj[5] работает
def __setitem__(self, index, value):
self._items[index] = value # Теперь obj[5] = 10 работает
def __contains__(self, item):
return item in self._items # Теперь 5 in obj работает
После этого с объектом можно работать почти как со встроенным списком — удивление пиздец у тех, кто не в теме.
4. Операторы:
А вот это уже высший пилотаж. Хочешь, чтобы твои объекты складывались +, сравнивались == или даже умножались *? Пожалуйста! Главное — логику не сломать.
class Money:
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def __add__(self, other):
if self.currency != other.currency:
raise ValueError("Валюты должны совпадать, ёпта!") # Защита от дурака
return Money(self.amount + other.amount, self.currency)
def __eq__(self, other):
return (self.amount == other.amount and
self.currency == other.currency) # Честное сравнение
Теперь salary1 + salary2 работает, и это выглядит охуенно естественно.
5. Контекстные менеджеры:
Это, наверное, одна из самых полезных фич. with — это гениально. А чтобы твой класс работал с with, нужно всего два метода: __enter__ и __exit__. Первый запускается при входе в блок, второй — при выходе, даже если внутри всё пошло по пизде и вылетело исключение.
class Timer:
def __enter__(self):
self.start = time.time()
return self # Этот объект попадёт в переменную после 'as'
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.time() - self.start
print(f"Время выполнения: {self.elapsed:.2f} сек") # Всё, засекли
# Использование:
with Timer() as timer:
# выполнение кода, который надо засечь
time.sleep(1)
# Тут уже вышли, время напечаталось
Удобство — овердохуища. Для файлов, соединений, транзакций — везде.
6. Вызываемые объекты:
А вот это вообще прикольно. Когда твой объект можно вызывать как функцию, просто поставив скобки. Реализуешь __call__ — и всё, объект стал функцией.
class Polynomial:
def __init__(self, coefficients):
self.coeffs = coefficients # Коэффициенты многочлена
def __call__(self, x):
# Считаем значение многочлена в точке x
return sum(c * (x ** i) for i, c in enumerate(self.coeffs))
poly = Polynomial([1, 2, 3]) # Создали многочлен 1 + 2x + 3x²
result = poly(2) # Вызвали его как функцию! 1 + 4 + 12 = 17
В общем, чувак, если подвести итог: в реальной работе чаще всего приходится реализовывать __init__ (ну это само собой), __str__ с __repr__ (чтоб не было стыдно), __eq__ (чтоб сравнивать) и методы для контейнеров, если делаешь что-то своё. Но самые мощные, на мой взгляд, — это __enter__/__exit__ для работы с ресурсами (прям спасают от утечек) и __call__, который превращает твой серьёзный объект в удобную "функцию". Как-то так, бля.