Какие магические методы в Python вы знаете?

Ответ

В 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__, который превращает твой серьёзный объект в удобную "функцию". Как-то так, бля.