Метаклассы и когда / как вызываются функции - PullRequest
0 голосов
/ 31 мая 2018

Я пытаюсь узнать, как работают метаклассы в Python 3. Вещи, которые я хочу знать: какие функции вызываются, в каком порядке, а также их сигнатуры и возвращаемые значения.

В качестве примера я знаю__prepare__ вызывается, когда создается экземпляр класса с метаклассом с аргументами metaclass, name_of_subclass, bases и возвращает словарь, представляющий будущее пространство имен экземпляра объекта.

Мне кажется, я хорошо понимаю шаг __prepare__ в процессе.Чего я не делаю, так это __init__, __new__ и __call__.Каковы их аргументы?Что они возвращают?Как они все называют друг друга, или вообще как идет процесс?В настоящее время я застрял в понимании, когда вызывается __init__.

Вот код, с которым я возился, чтобы ответить на мои вопросы:

#!/usr/bin/env python3

class Logged(type):

    @classmethod
    def __prepare__(cls, name, bases):
        print('In meta __prepare__')
        return {}

    def __call__(subclass):
        print('In meta __call__')
        print('Creating {}.'.format(subclass))
        return subclass.__new__(subclass)

    def __new__(subclass, name, superclasses, attributes, **keyword_arguments):
        print('In meta __new__')
        return type.__new__(subclass, name, superclasses, attributes)

    def __init__(subclass, name, superclasses, attributes, **keyword_arguments):
        print('In meta __init__')

class Thing(metaclass = Logged):

    def __new__(this, *arguments, **keyword_arguments):
        print('In sub __new__')
        return super(Thing, this).__new__(this)

    def __init__(self, *arguments, **keyword_arguments):
        print('In sub __init__')

    def hello(self):
        print('hello')

def main():
    thing = Thing()
    thing.hello()

if __name__ == '__main__':
    main()

Из этого и некоторыхgoogling, я знаю, что __new__ на самом деле является статическим методом, который возвращает экземпляр некоторого объекта (обычно это объект, в котором определен __new__, но не всегда), и что __init__ вызывается из экземпляра, когда он сделан,По этой логике меня смущает, почему Thing.__init__() не вызывается.Может ли кто-нибудь осветить?

Вывод этого кода выводит «привет», поэтому создается экземпляр Thing, что еще больше сбивает меня с толку насчет init.Вот вывод:

In meta __prepare__
In meta __new__
In meta __init__
In meta __call__
Creating <class '__main__.Thing'>
In sub __new__
hello

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

Ответы [ 2 ]

0 голосов
/ 31 мая 2018

Прежде всего: __prepare__ является необязательным, вам не нужно указывать реализацию, если все, что вы делаете, это возвращаете {} пустой словарь по умолчанию.

Метаклассы работают в точности какклассы , в том, что когда вы их вызываете, то они производят объект.И классы, и метаклассы являются фабриками .Разница в том, что метакласс создает объект класса при вызове, класс создает экземпляр при вызове.

И классы, и метаклассы определяют реализацию по умолчанию __call__, которая в основном делает:

  1. Вызовите self.__new__ для создания нового объекта.
  2. , если этот новый объект является экземпляром self / класса с этим метаклассом, тогда также вызывает __init__ для этого объекта.

Вы создали собственную реализацию __call__, которая не реализует этот второй шаг, поэтому Thing.__init__ никогда не вызывается.

Вы можете спросить: но метод __call__ определен в метаклассе .Это правильно, поэтому именно именно тот метод вызывается, когда вы вызываете класс с Thing().Все специальные методы (начинающиеся и заканчивающиеся __) вызываются для типа (например, type(instance) - это класс, а type(class) - это метакласс) именно потому, что в Python есть эта многоуровневая иерархия:экземпляры из классов из метаклассов;метод __call__ в самом классе используется для вызова экземпляров.Для вызовов metaclass() сам объект type обеспечивает реализацию __call__.Правильно, метаклассы являются одновременно подклассами и экземплярами type.

При написании метакласса вы должны реализовывать __call__, только если вы хотите настроить то, что происходит при вызове класса.В противном случае оставьте его в реализации по умолчанию.

Если я удаляю метод __call__ из вашего метакласса (и игнорирую метод __prepare__), то Thing.__init__ снова вызывается:

>>> class Logged(type):
...     def __new__(subclass, name, superclasses, attributes, **keyword_arguments):
...         print('In meta __new__')
...         return type.__new__(subclass, name, superclasses, attributes)
...     def __init__(subclass, name, superclasses, attributes, **keyword_arguments):
...         print('In meta __init__')
...
>>> class Thing(metaclass = Logged):
...     def __new__(this, *arguments, **keyword_arguments):
...         print('In sub __new__')
...         return super(Thing, this).__new__(this)
...     def __init__(self, *arguments, **keyword_arguments):
...         print('In sub __init__')
...     def hello(self):
...         print('hello')
...
In meta __new__
In meta __init__
>>> thing = Thing()
In sub __new__
In sub __init__
0 голосов
/ 31 мая 2018

В методе метакласса __call__ вы вызываете только Thing __new__, но не __init__.Кажется, что поведение по умолчанию __call__ состоит в том, чтобы вызывать их обоих, как видно, когда мы вызываем унаследованный метакласс __call__:

    def __call__(subclass):
        print('In meta __call__')
        print('Creating {}.'.format(subclass))
        return super().__call__(subclass)

. Это печатает:

Creating <class '__main__.Thing'>.
In sub __new__
In sub __init__
...