Правила поиска для вызываемого класса: A () против A .__ call __ () - PullRequest
0 голосов
/ 23 октября 2019

Я прочитал этот отличный пост , пытаясь лучше понять метаклассы, и столкнулся с некоторым поведением, которое меня смущает.

Настройка

class AMeta(type):
    # Disclaimer:  this is not what a real-world metaclass __call__ should look like!
    def __call__(self, *args):
        print("AMeta.__call__, self = {}, args = {}".format(self, args))

class A(metaclass=AMeta):
    def __call__(self, *args):
        print("A.__call__, self = {}, args = {}".format(self, args))

Вопрос 1

A.__call__(1, 2) печать A.__call__, self = 1, args = (2,).

Аргументы перенаправляются напрямую (1 появляется на месте себя), поэтому он появляетсяМеханизм дескриптора не запускается. На основании этих правил поиска это может быть связано с тем, что AMeta.__call__ является дескриптором без данных (и поэтому не имеет приоритета над A.__call__), или это может быть потому, что __call__ является магическим методом, которыйпоэтому, учитывая какой-то особый прямой поиск со стороны переводчика.

Я не уверен, какая из этих двух гипотез верна, если тоже. Если кто-нибудь знает ответ, я был бы признателен за это!

Вопрос 2 A(1, 2) печать AMeta.__call__, self = <class '__main__.A'>, args = (1, 2).

Вызывается __call__ метакласса, который соглашаетсяс изображением здесь из AMeta __call__, являющимся хозяином марионеток при создании A экземпляров.

Почему метакласс __call__ вызывается здесь? Какое правило поиска действует при вызове A(1, 2) в стиле конструктора? Ясно, что A(1, 2) - это не просто синтаксический сахар для A.__call__(1, 2).

Я видел ряд других вопросов, которые танцуют вокруг этих вопросов, но ни один из них не отвечает на них напрямую.

Iиспользую / занимаюсь Python 3.7 +.

1 Ответ

1 голос
/ 23 октября 2019

Вопрос 1:

Хотя вы действительно правы в том, что __call__ - это магический метод, который специально обрабатывается интерпретатором, это специальное поведение срабатывает только тогда, когда вы говорите A() и неявно делегируете__call__ метод. Выражение A.__call__ выполняется точно так же, как и a.foo, что означает, что, как вы сказали, AMeta.__call__ является дескриптором без данных и поэтому перезаписывается явным определением A.__call__

Вопрос 2:

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

class A:
    def __init__(self):
        self.__call__ = self.call2
    def call2(self, *args):
        print("call2, self = {}, args = {}".format(self, args))
    def __call__(self, *args):
        print("__call__, self = {}, args = {}".format(self, args))

Специальная обработка магических методов, о которых я говорил выше, заключается в том, что при попытке неявного вызова магических методов с использованием синтаксиса, такого как A(1, 2), Python игнорируетлюбые атрибуты, установленные непосредственно на объекте, и заботятся только об атрибутах, установленных на типе.

Таким образом, A(1, 2) является сахаром примерно для type(A).__call__(A, 1, 2) (за исключением того факта, что любые __getattr__ и __getattribute__методы по типу игнорируются). Это объясняет, почему вызывается метод __call__ в метаклассе.

...