← Назад к вопросам
Порядок вызова операторов
2.2 Middle🔥 81 комментариев
#Python Core
Условие
Какая последовательность вызова операторов в выражении:
a * b * c
Задача
- Объясните порядок вычисления (слева направо или справа налево?)
- Какой метод вызывается: mul или rmul?
- Что произойдёт, если a.mul(b) вернёт NotImplemented?
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Порядок Вызова Операторов в Python
Это глубокий вопрос о том, как Python интерпретирует бинарные операции и вызывает магические методы.
Часть 1: Порядок Вычисления
Выражение a * b * c вычисляется СЛЕВА НАПРАВО:
a * b * c → (a * b) * c
Это называется левая ассоциативность (left-to-right associativity).
Процесс:
- Вычисляем
a * b→ результат temp - Вычисляем
temp * c→ финальный результат
class Custom:
def __init__(self, value):
self.value = value
def __mul__(self, other):
print(f"__mul__: {self.value} * {other.value}")
return Custom(self.value * other.value)
a = Custom(2)
b = Custom(3)
c = Custom(4)
result = a * b * c
Вывод:
__mul__: 2 * 3
__mul__: 6 * 4
Часть 2: Какой Метод Вызывается?
Для a * b вызывается именно a.__mul__(b)
Порядок попыток:
a * b → a.__mul__(b)
if NotImplemented: b.__rmul__(a)
__mul__ — основной метод (левый операнд)
__rmul__ — отражённый метод (вызывается, если левый вернул NotImplemented)
class A:
def __mul__(self, other):
print(f"A.__mul__ called")
return "A mul"
class B:
def __rmul__(self, other):
print(f"B.__rmul__ called")
return "B rmul"
a = A()
b = B()
print(a * b) # Выведет: A.__mul__ called, результат: A mul
print(b * a) # Выведет: A.__mul__ called (через __rmul__ B), результат: A mul
Часть 3: Цепочка Разрешения (Resolution Chain)
Если a.__mul__(b) вернёт NotImplemented, Python попробует b.__rmul__(a):
class Vector:
def __init__(self, value):
self.value = value
def __mul__(self, other):
if isinstance(other, Vector):
return Vector(self.value * other.value)
return NotImplemented # Не можем умножать на этот тип
def __rmul__(self, other):
print("__rmul__ called")
return Vector(other * self.value)
def __repr__(self):
return f"Vector({self.value})"
v = Vector(5)
scalar = 3
print(v * scalar) # v.__mul__(3) → NotImplemented → scalar.__rmul__(v) ❌ (int не имеет __rmul__)
print(scalar * v) # scalar.__mul__(v) → NotImplemented → v.__rmul__(3) → Vector(15) ✓
Вывод:
__rmul__ called
Vector(15)
Полный Процесс Разрешения
Для выражения x * y:
- Python вызывает
x.__mul__(y) - Если возвращен результат (не NotImplemented) → вернуть его
- Если возвращен NotImplemented → перейти к шагу 4
- Python вызывает
y.__rmul__(x) - Если возвращен результат (не NotImplemented) → вернуть его
- Если возвращен NotImplemented → выбросить TypeError
class A:
def __mul__(self, other):
print(f"A.__mul__ called with {type(other).__name__}")
return NotImplemented
class B:
def __rmul__(self, other):
print(f"B.__rmul__ called with {type(other).__name__}")
return "Success from B"
class C:
pass
a = A()
b = B()
c = C()
print(a * b) # A.__mul__, потом B.__rmul__ → "Success from B"
print(a * c) # A.__mul__, потом C.__rmul__ (не существует) → TypeError
Практический Пример: Матрица и Скаляр
class Matrix:
def __init__(self, data):
self.data = data
def __mul__(self, other):
if isinstance(other, (int, float)):
return Matrix([[x * other for x in row] for row in self.data])
return NotImplemented
def __rmul__(self, other):
# Скалярное умножение коммутативно
return self.__mul__(other)
def __repr__(self):
return f"Matrix({self.data})"
m = Matrix([[1, 2], [3, 4]])
print(m * 5) # Matrix.__mul__(5) → успех
print(5 * m) # int.__mul__(Matrix) → NotImplemented → Matrix.__rmul__(5) → успех
Ассоциативность Для Разных Операторов
# Все эти операторы имеют ЛЕВУЮ ассоциативность
a + b + c → (a + b) + c # __add__, __radd__
a - b - c → (a - b) - c # __sub__, __rsub__
a * b * c → (a * b) * c # __mul__, __rmul__
a / b / c → (a / b) / c # __truediv__, __rtruediv__
a // b // c → (a // b) // c # __floordiv__, __rfloordiv__
a ** b ** c → a ** (b ** c) # __pow__, __rpow__ — СПРАВА НАЛЕВО!
Рекомендация
Для собеседования:
- Порядок вычисления: слева направо для
a * b * c→(a * b) * c - Метод: сначала
a.__mul__(b), потом если NotImplemented →b.__rmul__(a) - Цепочка: Python следует алгоритму разрешения, пока не найдёт работающий метод или не выбросит TypeError
Это показывает глубокое понимание протокола методов Python и того, как язык обрабатывает операции.