Магические (dunder) методы¶
Работа с NotImplemented для бинарных операций¶
Рассмотрим следующий код:
class Vector2D:
def __init__(self, x: int = 0, y: int = 0):
self.x = x
self.y = y
def __mul__(self, scalar: int):
return Vector2d(self.x * scalar, self.y * scalar)
Если в scalar попадёт float, то типы поменяются. Как правило, чтобы такого не было, можно добавить дополнительную проверку следующего вида:
Первой мыслью наверное будет "сделать raise NotImplemented
или AttributeError
", но она в корне не верна.
NotImplemented
- это специальное значение, которое должно возвращаться бинарными методами в том случае, если операция проводимая внутри метода не реализована по отношению к какому-то другому типу.
При возврате NotImplemented
интерпретатор попытается произвести "отраженную" операцию - для x.__mul__(y)
попытается вызвать y.__mul__(x)
. Как только все возможные методы вернут NotImplemented он сам возбудит исключение на месте вызова метода, а не в коде класса.
Поэтому, этот код надо переписать вот так:
class Vector2D:
def __init__(self, x: int = 0, y: int = 0):
self.x = x
self.y = y
def __mul__(self, scalar: int):
if isinstance(scalar, int):
return Vector2d(self.x * scalar, self.y * scalar)
else:
return NotImplemented # обратите внимание, здесь return, а не raise
После чего, "стек вызовов" будет выглядеть как-то так:
v = Vector2D(1, 1)
x = v * 23.4
# v.__mul__(23.4) -> return NotImplemented
# float(23.4).__mul__(v) -> return NotImplemented
# вариантов не осталось, интерпретатор делает raise TypeError
TypeError: unsupported operand type(s) for *: 'Vector2D' and 'float'
Подробнее можно почитать здесь
Когда использовать __str__
или __repr__
?¶
Оба магических метода используются для получения строкового представления объекта. __repr__
должен использоваться для предоставлении информации об объекте разработчику, а __str__
должен быть читаемым и использоваться для представления информации пользователю.