元类以及何时/如何调用函数

时间:2018-05-31 16:22:08

标签: python python-3.x metaprogramming metaclass

我试图了解元类在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()

从这个和一些谷歌搜索,我知道__new__实际上是一个静态方法,它返回一个对象的实例(通常是__new__定义的对象,但并非总是如此),{ {1}}在创建时被称为实例。按照这个逻辑,我很困惑为什么__init__没有被调用。有人会照亮吗?

此代码的输出打印' hello',因此正在创建Thing实例,这进一步让我对init感到困惑。这是输出:

Thing.__init__()

任何帮助理解元类都将不胜感激。我已经阅读了不少教程,但我已经错过了一些这些细节。

2 个答案:

答案 0 :(得分:5)

首先:__prepare__是可选的,如果您所做的只是返回默认的{}空字典,则不需要提供实现。

元类工作与类完全相同,因为当你调用它们时,它们会生成一个对象。类和元类都是工厂。不同之处在于,元类在调用时会生成一个类对象,一个类在调用时会生成一个实例。

类和元类都定义了一个默认的__call__实现,它实际上是:

  1. 调用self.__new__以生成新对象。
  2. 如果新对象是self / a类的实例 元类,然后在该对象上调用__init__
  3. 您制作了自己的__call__实施,但没有实施第二步,这就是永远不会调用Thing.__init__的原因。

    您可能会问:__call__方法是在元类上定义的。这是正确的,因此当您使用Thing()调用类时,正是正好调用的方法。所有特殊方法(以__开头和结尾)都是called on the type(例如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__
    

答案 1 :(得分:2)

在元类的__call__方法中,您只调用Thing的{​​{1}},而不是__new__。似乎__init__的默认行为是调用它们,就像我们调用元类的继承__call__时所看到的那样:

__call__

打印:

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