Что такое магические (dunder) методы в Python и для чего они используются?

Ответ

Магические методы, также известные как "dunder" методы (от "double underscore" — двойное подчеркивание), — это специальные методы в Python, имена которых начинаются и заканчиваются двойным подчеркиванием (например, __init__, __str__). Они позволяют классам реализовывать определенные протоколы и переопределять поведение встроенных операций и функций.

Назначение: Dunder методы используются для:

  • Операторной перегрузки: Изменение поведения стандартных операторов (например, +, -, ==, []) для пользовательских объектов.
  • Реализации протоколов: Позволяют объектам вести себя как контейнеры, итераторы, числовые типы и т.д., интегрируясь с функциями Python (например, len(), str(), for...in).
  • Управления жизненным циклом объекта: Инициализация, удаление, создание.

Основные категории и примеры:

  1. Инициализация и удаление объектов:

    • __new__(cls, ...): Вызывается для создания нового экземпляра класса.
    • __init__(self, ...): Конструктор, инициализирует созданный объект.
    • __del__(self): Деструктор, вызывается при удалении объекта (не гарантировано).
  2. Строковое представление:

    • __str__(self): Определяет "официальное" строковое представление объекта, используемое функцией str() и print(). Должно быть читабельным для пользователя.
    • __repr__(self): Определяет "неофициальное" строковое представление, используемое функцией repr() и в интерактивной консоли. Должно быть однозначным и, по возможности, позволять воссоздать объект.
  3. Сравнение объектов (операторы сравнения):

    • __eq__(self, other): self == other
    • __ne__(self, other): self != other
    • __lt__(self, other): self < other
    • __le__(self, other): self <= other
    • __gt__(self, other): self > other
    • __ge__(self, other): self >= other
  4. Контейнерные протоколы (доступ к элементам):

    • __len__(self): len(self)
    • __getitem__(self, key): self[key] (доступ по индексу/ключу)
    • __setitem__(self, key, value): self[key] = value
    • __delitem__(self, key): del self[key]
    • __contains__(self, item): item in self
  5. Арифметические операции:

    • __add__(self, other): self + other
    • __sub__(self, other): self - other
    • __mul__(self, other): self * other
    • И многие другие (__truediv__, __floordiv__, __mod__, __pow__ и т.д.).

Пример использования:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        """Представление для разработчика."""
        return f"Point(x={self.x}, y={self.y})"

    def __str__(self):
        """Представление для пользователя."""
        return f"({self.x}, {self.y})"

    def __add__(self, other):
        """Перегрузка оператора сложения для сложения двух точек."""
        if isinstance(other, Point):
            return Point(self.x + other.x, self.y + other.y)
        raise TypeError("Можно складывать только с объектом Point")

    def __eq__(self, other):
        """Перегрузка оператора равенства."""
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return NotImplemented # Позволяет другому объекту попробовать сравнение

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = Point(1, 2)

print(p1)         # Вызывает __str__: (1, 2)
print(repr(p1))   # Вызывает __repr__: Point(x=1, y=2)

p_sum = p1 + p2
print(f"Сумма точек: {p_sum}") # (4, 6)

print(f"p1 == p2: {p1 == p2}") # False
print(f"p1 == p3: {p1 == p3}") # True

Ответ 18+ 🔞

А, вот эта тема про магические методы, ёпта! Это же, блядь, основа основ, на которой всё держится, как тот самый Герасим на крыше. Ты думаешь, что твой объект — это просто куча атрибутов? Хуй там! Это целая вселенная, блядь, со своими законами, и эти законы — ду́ндеры.

Суть, на пальцах: Это такие методы, которые начинаются и заканчиваются двойным подчёркиванием. __init__, __str__, __add__ — вот это всё. Их ещё «дундер» называют, потому что «дабл андерскор». Ну, а магическими — потому что они, сука, реально творят магию. Они говорят Питону: «Слушай, падла, когда ты будешь пытаться сложить два моих объекта через +, не лезь со своим стандартным поведением, а сделай вот так, как я тебе сказал!»

Зачем это всё, нахуй? А чтобы твои объекты вели себя не как безмозглые куски памяти, а как настоящие граждане языка! Чтобы они умели:

  • Перегружать операторы: Сделать так, чтобы объект1 + объект2 делал не хуйню, а именно то, что тебе нужно. Складывал вектора, деньги, или, там, пиццы с ананасами, я не знаю.
  • Встраиваться в систему: Чтобы твой объект работал с len(), str(), for, in — со всеми этими стандартными штуками. Чтобы он вёл себя как контейнер, как число, как итератор — да как угодно!
  • Контролировать свою судьбу: Родиться красиво (__init__), предстать перед людьми в лучшем свете (__str__), и, когда придёт время, уйти с достоинством (ну, __del__, хотя на него надейся, но проверяй).

Основные банды дундеров:

  1. Родители и могильщики:

    • __new__(cls, ...): Это тот, кто создаёт объект из ничего. Редко трогаешь.
    • __init__(self, ...): А это — конструктор, наш главный по тарелочкам. Он берёт голый созданный объект и начиняет его данными. «Родился, блядь, вот тебе x и y, живи».
    • __del__(self): Деструктор. Вызывается, когда объект собирают на свалку истории. Ненадёжный тип, на него особо не рассчитывай.
  2. Пиар-отдел (строковое представление):

    • __str__(self): Для красоты. Что выведет print(объект) или str(объект). Должно быть человекочитаемо.
    • __repr__(self): Для отладки и разработчиков. Что покажет просто имя объекта в консоли или repr(объект). Должно быть однозначно и, в идеале, позволять скопипастить и создать такой же объект. Разницу чувствуешь? Один — для людей, другой — для таких же, как мы, ебланов, которые код дебажат.
  3. Судьи и сравниватели:

    • __eq__, __ne__, __lt__, __le__, __gt__, __ge__: Это всё про сравнение. ==, !=, <, <=, >, >=. Без них твои объекты даже сравнить нормально нельзя, пидарас шерстяной.
  4. Контейнерная братва:

    • __len__(self): Отвечает на вопрос len(объект).
    • __getitem__(self, key): Позволяет делать объект[ключ] — доставать элемент.
    • __setitem__(self, key, value): Позволяет делать объект[ключ] = значение — класть элемент.
    • __delitem__(self, key): del объект[ключ] — удалять.
    • __contains__(self, item): элемент in объект — проверять вхождение.
  5. Бухгалтерия (арифметика):

    • __add__, __sub__, __mul__ и куча других. +, -, *, /, //, %, ** — всё это можно переопределить. Хочешь, чтобы точки складывались? Реализуй __add__. Хочешь, чтобы матрицы перемножались? __mul__ тебе в помощь.

Ну и пример, чтобы не быть пиздаболом:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        """Для таких же ебланов-разработчиков, как я."""
        return f"Point(x={self.x}, y={self.y})"

    def __str__(self):
        """Для нормальных людей, которые просто хотят точку увидеть."""
        return f"({self.x}, {self.y})"

    def __add__(self, other):
        """Вот она, магия! Сложение точек, блядь!"""
        if isinstance(other, Point):
            return Point(self.x + other.x, self.y + other.y)
        raise TypeError("Слышь, мудила, можно складывать только с другой Point!")

    def __eq__(self, other):
        """А это чтобы точки на равенство проверять."""
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return NotImplemented  # Говорим: "Я не могу, пусть другой объект попробует"

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = Point(1, 2)

print(p1)         # Вызовет __str__: (1, 2) — красиво
print(repr(p1))   # Вызовет __repr__: Point(x=1, y=2) — точно

p_sum = p1 + p2   # Вызовет __add__ — вот она, перегрузка оператора!
print(f"Сумма: {p_sum}") # (4, 6)

print(f"p1 == p2: {p1 == p2}") # False, вызовет __eq__
print(f"p1 == p3: {p1 == p3}") # True

Вот и вся магия, блядь. Без этих методов твои классы — как немой Герасим: сила есть, а сказать нихуя не может. А с ними — уже полноценный участник питонячьего базара.