Как узнать, какой специальный (с двумя подчеркиваниями) метод вызывается при использовании оператора?

Рейтинг: 5Ответов: 2Опубликовано: 14.05.2023

К примеру, есть следующий код:

first_list = [1, 2]
second_list = [3, 4]
final_list = first_list + second_list

Вывод последней строки кода эквивалентен использованию специального метода __add__:

final_list = first_list.__add__(second_list)

Вот так я получу вывод всех аргументов и методов, доступных для этого объекта:

print(dir(final_list))

Я знаю, что в данном случае использовался метод __add__, но существует ли функция, возвращающая специальный метод, который был использован при выполнении выражения?

Ответы

▲ 6

Боюсь, что извлечь метод прямо из оператора не получится по причинам, которые будут описаны ниже, в примечаниях.
Но можно попробовать получить список функций, соответствующих операторам:

# Модуль operator содержит методы, аналогичные встроенным методам Питона и методам классов Питона
import operator
import re

# Будем искать в докстрингах методов фразу "Same as" ("То же, что и")
rx = re.compile('Same as (.*)')

# Перебираем имена модуля operator
for name in dir(operator):
    
    # Нас интересуют только имеющие двойное подчёркивание в названии
    if '__' in name:
        
        # Берём аттрибут модуля operator с таким именем
        attr = getattr(operator, name)
        
        # Читаем его docstring и ищем там фразу (см. выше)
        descr = rx.findall(attr.__doc__)
        
        # Если фраза нашлась, то она там одна и заканчивается она точкой, которая нам не нужна
        if descr:
            print(f'{descr[0][:-1]} -> {name}')

Вывод:

abs(a) -> __abs__
a + b -> __add__
a & b -> __and__
a + b, for a and b sequences -> __concat__
b in a (note reversed operands) -> __contains__
del a[b] -> __delitem__
a == b -> __eq__
a // b -> __floordiv__
a >= b -> __ge__
a[b] -> __getitem__
a > b -> __gt__
a += b -> __iadd__
a &= b -> __iand__
a += b, for a and b sequences -> __iconcat__
a //= b -> __ifloordiv__
a <<= b -> __ilshift__
a @= b -> __imatmul__
a %= b -> __imod__
a *= b -> __imul__
a.__index__( -> __index__
~a -> __inv__
~a -> __invert__
a |= b -> __ior__
a **= b -> __ipow__
a >>= b -> __irshift__
a -= b -> __isub__
a /= b -> __itruediv__
a ^= b -> __ixor__
a <= b -> __le__
a << b -> __lshift__
a < b -> __lt__
a @ b -> __matmul__
a % b -> __mod__
a * b -> __mul__
a != b -> __ne__
-a -> __neg__
not a -> __not__
a | b -> __or__
+a -> __pos__
a ** b -> __pow__
a >> b -> __rshift__
a[b] = c -> __setitem__
a - b -> __sub__
a / b -> __truediv__
a ^ b -> __xor__

И ещё этот же вывод в виде таблицы для удобства:

Оператор Метод
abs(a) __abs__
a + b __add__
a & b __and__
a + b, for a and b sequences __concat__
b in a (note reversed operands) __contains__
del a[b] __delitem__
a == b __eq__
a // b __floordiv__
a >= b __ge__
a[b] __getitem__
a > b __gt__
a += b __iadd__
a &= b __iand__
a += b, for a and b sequences __iconcat__
a //= b __ifloordiv__
a <<= b __ilshift__
a @= b __imatmul__
a %= b __imod__
a *= b __imul__
a.__index__( __index__
~a __inv__
~a __invert__
a |= b __ior__
a **= b __ipow__
a >>= b __irshift__
a -= b __isub__
a /= b __itruediv__
a ^= b __ixor__
a <= b __le__
a << b __lshift__
a < b __lt__
a @ b __matmul__
a % b __mod__
a * b __mul__
a != b __ne__
-a __neg__
not a __not__
a | b __or__
+a __pos__
a ** b __pow__
a >> b __rshift__
a[b] = c __setitem__
a - b __sub__
a / b __truediv__
a ^ b __xor__

P.S. Убрал методы без двойного подчёркивания, они к данному вопросу не имеют отношения.
P.P.S. Обратите внимание, что в случае сложения списков должен был бы быть вызван не метод __add__, а метод __concat__. Но и он по факту не будет вызван, как можно убедиться с помощью модуля dis (жалко ответ про это отвечавший удалил). Питон оптимизирует довольно много разных операций при работе и со списками и с обычными переменными, зачастую поэтому при работе операторов не вызываются методы объектов, которые делают эту операцию, а вызываются специальные внутренние методы самого Питона, что позволяет сэкономить чуть-чуть времени на то, чтобы не искать ссылки на эти методы через их названия в списке переменных.

Код проверки, что __add__ в случае использования оператора + не будет вызван:

import dis

dis.dis("first_list = [1, 2]; second_list = [3, 4]; final_list = first_list + second_list")
#             16 LOAD_NAME                0 (first_list)
#             18 LOAD_NAME                1 (second_list)
#             20 BINARY_ADD

dis.dis("first_list = [1, 2]; second_list = [3, 4]; final_list = first_list.__add__(second_list)")
#             16 LOAD_NAME                0 (first_list)
#             18 LOAD_METHOD              2 (__add__)
#             20 LOAD_NAME                1 (second_list)
#             22 CALL_METHOD              1
▲ -2

Как такового "быстрого и удобного" метода, чтобы узнать какой магический метод используется вместо оператора "+", к сожалению, нет. Для стандартных типов (list, int, NumPy.array) нужно смотреть документацию или просто гуглить, так в разы быстрее. Со временем в мозгу нейроны сделают своё дело и достаточно будет лишь вспомнить.