11 Магических методов Python, которые должен знать каждый программист

11 Волшебных методов Python, которые должен знать каждый программист

В Python магические методы помогают эмулировать поведение встроенных функций в ваших классах Python. Эти методы имеют двойные подчеркивания в начале и конце (__), и поэтому они также называются методами-дандерами.

Эти магические методы также помогают вам реализовать перегрузку операторов в Python. Вы, наверное, видели примеры этого. Например, использование оператора умножения * с двумя целыми числами дает произведение. А использование его со строкой и целым числом k дает повторение строки k раз:

 >>> 3 * 412>>> 'code' * 3'codecodecode'

В этой статье мы рассмотрим магические методы в Python, создав простой двумерный векторный класс Vector2D.

Мы начнем с методов, с которыми вы, вероятно, уже знакомы, и постепенно перейдем к более полезным магическим методам.

Давайте начнем писать некоторые магические методы!

1. __init__

Рассмотрим следующий класс Vector2D:

class Vector2D:    pass

После создания класса и создания объекта вы можете добавлять атрибуты так: obj_name.attribute_name = value.

Однако, вместо того, чтобы вручную добавлять атрибуты к каждому созданному вами экземпляру (что, конечно же, неинтересно!), вам нужен способ инициализировать эти атрибуты при создании объекта.

Для этого вы можете определить метод __init__. Давайте определим метод __init__ для нашего класса Vector2D:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = yv = Vector2D(3, 5)

2. __repr__

Когда вы пытаетесь проверить или вывести на печать объект, который вы создали, вы видите, что вы не получаете никакой полезной информации.

v = Vector2D(3, 5)print(v)

Output >>> <__main__.Vector2D object at 0x7d2fcfaf0ac0>

Поэтому вы должны добавить строковое представление, строковое представление объекта. Для этого добавьте метод __repr__ вот так:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"v = Vector2D(3, 5)print(v)

Output >>> Vector2D(x=3, y=5)

Метод __repr__ должен включать все атрибуты и информацию, необходимую для создания экземпляра класса. Метод __repr__ обычно используется в целях отладки.

3. __str__

Метод __str__ также используется для добавления строкового представления объекта. В общем, метод __str__ используется для предоставления информации конечным пользователям класса.

Добавим метод __str__ к нашему классу:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __str__(self):        return f"Vector2D(x={self.x}, y={self.y})"v = Vector2D(3, 5)print(v)

 

Вывод >>> Vector2D(x=3, y=5)

 

Если нет реализации __str__, то будет использоваться __repr__. Так что для каждого создаваемого класса необходимо, как минимум, добавить метод __repr__

 

4. __eq__

 

Затем добавим метод для проверки равенства любых двух объектов класса Vector2D. Два векторных объекта равны, если у них идентичные координаты x и y.

Теперь создайте два объекта Vector2D с одинаковыми значениями для x и y и сравните их на равенство:

v1 = Vector2D(3, 5)v2 = Vector2D(3, 5)print(v1 == v2)

 

Результат – False. По умолчанию сравнение проверяет равенство идентификаторов объекта в памяти.

Вывод >>> False

 

Добавим метод __eq__ для проверки равенства:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __eq__(self, other):        return self.x == other.x and self.y == other.y

 

Теперь проверка на равенство должна работать ожидаемым образом:

v1 = Vector2D(3, 5)v2 = Vector2D(3, 5)print(v1 == v2)

 

Вывод >>> True 

 

5. __len__

 

Встроенная функция Python len() помогает вычислить длину встроенных итерируемых объектов. Для вектора длина должна возвращать количество элементов, содержащихся вектором. 

Поэтому добавим метод __len__ для класса Vector2D:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __len__(self):        return 2v = Vector2D(3, 5)print(len(v))

 

Все объекты класса Vector2D имеют длину 2:

Вывод >>> 2

 

6. __add__

 

Теперь давайте подумаем о типичных операциях, которые мы выполняем над векторами. Добавим магические методы для сложения и вычитания любых двух векторов.

Если вы попытаетесь сложить два векторных объекта непосредственно, то получите ошибку. Поэтому следует добавить метод __add__:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __add__(self, other):        return Vector2D(self.x + other.x, self.y + other.y)

 

Теперь можно сложить любые два вектора следующим образом:

v1 = Vector2D(3, 5)v2 = Vector2D(1, 2)result = v1 + v2print(result)

 

Вывод >>> Vector2D(x=4, y=7)

 

7. __sub__

 

Далее давайте добавим метод __sub__ для вычисления разницы между любыми двумя объектами класса Vector2D:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __sub__(self, other):        return Vector2D(self.x - other.x, self.y - other.y)

 

v1 = Vector2D(3, 5)v2 = Vector2D(1, 2)result = v1 - v2print(result)

 

Вывод >>> Vector2D(x=2, y=3)

 

8. __mul__

 

Мы также можем определить метод __mul__ для определения умножения между объектами.

Давайте реализуем его для выполнения следующих операций:

  • Скалярное умножение: умножение вектора на скаляр и 
  • Скалярное произведение: скалярное произведение двух векторов.
class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __mul__(self, other):        # Скалярное умножение        if isinstance(other, (int, float)):            return Vector2D(self.x * other, self.y * other)        # Скалярное произведение        elif isinstance(other, Vector2D):            return self.x * other.x + self.y * other.y        else:            raise TypeError("Unsupported operand type for *")

 

Теперь рассмотрим несколько примеров для демонстрации метода __mul__ в действии.

v1 = Vector2D(3, 5)v2 = Vector2D(1, 2)# Скалярное умножениеresult1 = v1 * 2print(result1)  # Скалярное произведениеresult2 = v1 * v2print(result2)

 

Вывод >>>Vector2D(x=6, y=10)13

 

9. __getitem__

 

Магический метод __getitem__ позволяет индексировать объекты и получать доступ к атрибутам или срезам атрибутов с использованием квадратных скобок [ ].

Для объекта v класса Vector2D:

  • v[0]: координата x
  • v[1]: координата y

Если вы попытаетесь получить доступ по индексу, вы получите ошибку:

v = Vector2D(3, 5)print(v[0],v[1])

 

---------------------------------------------------------------------------TypeError                              Traceback (most recent call last) in ()----> 1 print(v[0],v[1])TypeError: 'Vector2D' object is not subscriptable

 

Давайте реализуем метод __getitem__

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __getitem__(self, key):        if key == 0:            return self.x        elif key == 1:            return self.y        else:            raise IndexError("Index out of range")

 

Теперь вы можете получить доступ к элементам с помощью индексов, так как показано ниже:

v = Vector2D(3, 5)print(v[0])  print(v[1])

 

Вывод >>>35

10. __call__

С помощью реализации метода __call__ вы можете вызывать объекты, как если бы они были функциями.

В классе Vector2D мы можем реализовать __call__, чтобы масштабировать вектор на заданный коэффициент:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y         def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __call__(self, scalar):        return Vector2D(self.x * scalar, self.y * scalar)

Так что если вы сейчас вызовете 3, вы получите вектор, умноженный на 3:

v = Vector2D(3, 5)result = v(3)print(result)

Output >>> Vector2D(x=9, y=15)

11. __getattr__

Метод __getattr__ используется для получения значений конкретных атрибутов объектов.

В этом примере мы можем добавить магический метод __getattr__, который вызывается для вычисления величины (L2-нормы) вектора:

class Vector2D:    def __init__(self, x, y):        self.x = x        self.y = y    def __repr__(self):        return f"Vector2D(x={self.x}, y={self.y})"    def __getattr__(self, name):        if name == "magnitude":            return (self.x ** 2 + self.y ** 2) ** 0.5        else:            raise AttributeError(f"Объект 'Vector2D' не имеет атрибута '{name}'")

Проверим, работает ли это ожидаемым образом:

v = Vector2D(3, 4)print(v.magnitude)

Output >>> 5.0

Заключение

Вот и всё для этого учебного пособия! Надеюсь, вы научились добавлять магические методы в свой класс, чтобы эмулировать поведение встроенных функций.

Мы рассмотрели некоторые из самых полезных магических методов. Однако это не исчерпывающий список. Чтобы более глубоко понять, создайте собственный класс на Python и добавьте магические методы в зависимости от необходимой функциональности. Продолжайте писать код!

[Bala Priya C](https://twitter.com/balawc27) – разработчик и технический писатель из Индии. Ей нравится работать в области математики, программирования, науки о данных и контента. Ее интересы и экспертиза включают DevOps, науку о данных и обработку естественного языка. Она любит чтение, письмо, кодирование и кофе! В настоящее время она работает над обучением и делится своими знаниями с сообществом разработчиков, создавая учебники, руководства, мнения и многое другое.