Я думал, что для объекта Python obj() эквивалентно obj.__call__(). Но похоже, что он не действует при использовании setattr.
In [46]: class A:
...: def __call__(self):
...: return 1
...:
In [47]: a = A()
In [48]: a()
Out[48]: 1
In [49]: a.__call__()
Out[49]: 1
In [50]: getattr(a, '__call__')
Out[50]: <bound method A.__call__ of <__main__.A object at 0x10a3d2a00>>
In [51]: setattr(a, '__call__', lambda: 100)
In [52]: a()
Out[52]: 1
In [53]: a.__call__()
Out[53]: 100
Почему это так? И как я могу установить метод вызова во время выполнения?
Обратите внимание, что в версии Python — 3.9.18 и 3.11.4.
Я ожидал, что фрагмент кода выше a() вернет 100






Цитирую документацию, курсив мой:
object.__call__(self[, args...])Вызывается, когда экземпляр «вызывается» как функция; если этот метод определен,
x(arg1, arg2, ...)примерно переводится какtype(x).__call__(x, arg1, ...).
IOW, новый набор __call__ для экземпляра объекта не будет учитываться (и setattr здесь не играет роли; вы получите то же самое поведение с a.__call__ = ...).
Проблема в том, что __call__ определяется на уровне класса, а не на уровне экземпляра. См. документацию (из Python 2, когда она была представлена) здесь.
Вам нужно обязательно определить функцию вызова на уровне экземпляра и применить к ней setattr:
class MyClass:
def __call__(self, *args, **kwargs):
return self._call(self, *args, **kwargs)
>>> a = MyClass()
>>> setattr(a, '_call', lambda self: 100)
>>> a()
100
Или вы можете исправить сам класс, если хотите, чтобы каждый экземпляр MyClass() имел одну и ту же функцию __call__. Однако подумайте внимательно, хотите ли вы это сделать:
>>> setattr(MyClass, __call__, lambda self: "foo")
>>> a()
"foo"
Объяснение: Приоритет определения класса __call__ над любым определением, специфичным для экземпляра, является общим для различных имен специальных методов/перегрузок операторов в модели данных Python .
Это произошло, когда в Python 2 были введены классы нового стиля. Процитируем «порядок приоритета», введенный в (PEP 252):
Существует конфликт между именами, хранящимися в экземпляре dict, и именами, хранящимися в типе dict. Если в обоих словарях есть запись с одним и тем же ключом, какой из них нам следует вернуть? [...]
- Посмотрите тип dict. Если вы нашли дескриптор данных, используйте его метод get() для получения результата. Это заботится о специальных атрибутах, таких как dict и class.
- Посмотрите в экземпляре dict. Если вы что-нибудь найдете, то это все. (Это учитывает требование, согласно которому обычно словарь экземпляра переопределяет словарь класса.)
«Тип dict» относится к определениям уровня класса, таким как определение __call__ MyClass, а «экземпляр dict» относится к значениям, которые мы установили для конкретного экземпляра MyClass, например a выше.
В вашем вопросе исходное определение __call__ исходит из шага 1 (тип dict), в то время как любые значения, которые вы устанавливаете таким образом, устанавливаются в экземпляре dict, который имеет более низкий приоритет:
setattr(a, '__call__', lambda self: 100)
a.__call__ = lambda self: 100
Вы можете найти дополнительную информацию о том, как это произошло, в связанном вопросе StackOverflow , который также указывает на эту интересную внешнюю статью.