как метакласс работает со списком MRO, когда вызывается super ()? - PullRequest
2 голосов
/ 20 июня 2019

Меня действительно смущает следующий пример кода:

class Meta_1(type):
    def __call__(cls, *a, **kw):             # line 1
        print("entering Meta_1.__call__()")  

        print(cls)                           # line 4
        print(cls.mro())                     # line 5
        print(super(Meta_1, cls).__self__)   # line 6

        rv = super(Meta_1, cls).__call__(*a, **kw)  # line 7
        print("exiting Meta_1.__call__()")
        return rv


class Car(object, metaclass=Meta_1):

    def __new__(cls, *a, **kw):
        print("Car.__new__()")
        rv = super(Car, cls).__new__(cls, *a, **kw)
        return rv

    def __init__(self, *a, **kw):
        print("Car.__init__()")
        super(Car,self).__init__(*a, **kw)

if __name__ == '__main__':

    c = Car()

Сообщение для печати этого кода:

entering Meta_1.__call__()
<class '__main__.Car'>                      # line 4
[<class '__main__.Car'>, <class 'object'>]  # line 5
<class '__main__.Car'>                      # line 6
Car.__new__()
Car.__init__()
exiting Meta_1.__call__()

Результат показывает, что clsстроки 4 - это класс Car, а его список MRO:
[<class '__main__.Car'>, <class 'object'>]

Однако строка 6 показывает, что super(Meta_1, cls).__self__ также является классом Car.

Я действительно запутался, что:

  1. В строке 7 кажется, что super(Meta_1, cls).__call__(*a, **kw) в итоге приведет к type.__call__.Но, насколько мне известно, super(arg1, arg2) изучит MRO второго входного аргумента, чтобы найти первый входной аргумент, и вернет ему следующий класс.Но в строках 6 и 7 моего кода, MRO для 2-го аргумента (Car), не содержится 1-й входной аргумент (Meta_1), вы не можете найти Meta_1 в MRO для Car.так почему бы super(Meta_1, cos) заставить нас вызвать type.__call__ ??

2.если super(Meta_1, cls).__self__ класс Car, то строка 7 означает, что вызывается Car __call__?Но вызов класса Car привел нас к первой строке, верно?это не было бы петлей?

Ответы [ 3 ]

1 голос
/ 21 июня 2019

Важно обратить внимание на то, какие значения используются в качестве каждого аргумента для super.Основная цель super состоит в том, чтобы выполнить поиск атрибута согласно некоторому порядку разрешения метода (MRO).Второй аргумент определяет , какую MRO использовать;первый определяет, где начать поиск.

MRO всегда определяется классом ;при выполнении разрешения метода для экземпляра мы используем MRO класса, типом которого является этот экземпляр.

В классе

class Meta_1(type):
    def __call__(cls, *a, **kw):             # line 1
        print("entering Meta_1.__call__()")  

        print(cls)                           # line 4
        print(cls.mro())                     # line 5
        print(super(Meta_1, cls).__self__)   # line 6

        rv = super(Meta_1, cls).__call__(*a, **kw)  # line 7
        print("exiting Meta_1.__call__()")
        return rv

мы видим два варианта использования super.Оба принимают одинаковые аргументы.cls - это некоторый объект, переданный в качестве первого аргумента Meta_1.__call__.Это означает, что мы будем использовать MRO, предоставленный type(cls), и мы будем использовать первый найденный класс после Meta_1, который предоставляет требуемый метод.(В первом вызове __self__ является атрибутом самого прокси-объекта, а не атрибутом или методом класса, для которого возвращается прокси super.)

Когда вы запускаете свой код, вы видите, чтоcls привязан к вашему объекту типа Car.Это потому, что Car() реализован type(Car).__call__();поскольку Car использует Meta_1 в качестве своего метакласса, type(Car) равен Meta_1.

cls.mro() не имеет значения, потому что это MRO, используемое экземплярами из cls.

MRO самого Meta_1 можно увидеть с помощью

>>> Meta_1.mro(Meta_1)
[<class '__main__.Meta_1'>, <class 'type'>, <class 'object'>]

(mro - это метод экземпляра класса type, и поэтому требует, казалось бы, избыточного экземпляра type в качестве аргумента. Имейте в виду, что cls.mro() эквивалентно type(cls).mro(cls).)

Таким образом, строка 7 является вызовом type.__call__, чтобы создать экземпляр cls, который Meta_1.__call__ может вернуться.

1 голос
/ 20 июня 2019

Вы путаете несколько понятий. Первый из них путает метакласс с иерархией наследования классов.

Обе вещи являются ортогональными - если взглянуть на mro Car, вы увидите дерево наследования для этого класса, которое не включает метакласс. Другими словами, ни Meta_1 ни в коем случае не должно быть в MRO (или в дереве наследования).

Метакласс - это тип класса, то есть он имеет шаблоны и методы для создания самого объекта класса. Как таковой, он имеет «механизмы» для создания самого класса MRO и для вызова класса '__new__ и __init____init_subclass__ и инициализации дескрипторов, вызывающих их __set_name__).

Итак, вызов объекта класса, как и вызов любого экземпляра в Python, запустит код в своем методе класса __call__. В случае с классом случается так, что «вызов» класса - это способ создания нового экземпляра - и что это за метакласс '__call__.

Другая вещь, которую вы неправильно понимаете, это super() объект. Super() на самом деле не суперкласс, не экземпляр суперкласса - это скорее прокси-объект, который будет передавать любой поиск атрибута или вызов метода в методы и атрибуты соответствующего суперкласса. В качестве части механизма, который super() использует, чтобы иметь возможность выступать в качестве прокси-сервера, он должен иметь экземпляр , где он называется своим собственным атрибутом __self__. Другими словами, атрибут __self__ является обычным атрибутом (прокси) объекта, возвращаемого вызовом super() - он выбирается из второго аргумента или автоматически в Python 3 - и используется внутри системы, когда super Объект используется в качестве прокси для получения действия, как если бы он обращался к атрибутам или методам «суперкласса» этого экземпляра. (Экземпляр аннотирован в __self__).

Когда вы используете super() внутри метакласса, прокси-класс является суперклассом метакласса, который равен type, а не суперклассом Car, object.

И так к вашему второму вопросу:

  1. если super(Meta_1, cls).__self__ - это класс Car, то строка 7 означает, что это __call__ автомобиля, который вызывается? Но зовет машину класс вывел нас на первую строчку, верно? не будет ли это петля

Как было сказано выше, вызов super() из метакласса '__call__ вызовет type.__call__, и он получит класс Car в качестве параметра cls. Этот метод, в свою очередь, будет запускать Car.__new__ и Car.__init__ как обычный процесс для создания экземпляра класса.

0 голосов
/ 23 июня 2019

Это отличный ответ из оригинального поста Michael Ekoka , откуда пришел мой пример кода: Использование метода __call__ метакласса вместо __new __?

По сути, мне нужно лучше понять, как работает super().

цитата:

super действительно будет использовать cls, чтобы найти MRO, но не так, как можно подумать. Я предполагаю, что вы думали, что он сделает что-то прямое, как cls.__mro__ и найдет Meta_1. Не так, это MRO Class_1, который вы решаете, выполняя это, другой, не связанный MRO, и Meta_1 не является его частью (Class_1 не наследуется от Meta_1). cls даже наличие свойства __mro__ - это просто случайность, поскольку оно является классом. Вместо этого super будет искать класс (метакласс в нашем случае) cls, то есть Meta_1, а затем будет искать MRO оттуда (то есть Meta_1.__mro__).

...