【问题标题】:Metaclasses and when/how functions are called元类以及何时/如何调用函数
【发布时间】:2018-11-10 17:31:03
【问题描述】:

我正在尝试学习元类在 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__的对象,但并非总是如此),并且调用__init__制作时的一个实例。按照这种逻辑,我对为什么不调用 Thing.__init__() 感到困惑。有人可以照亮吗?

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

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

任何理解元类的帮助将不胜感激。我已经阅读了很多教程,但我错过了其中的一些细节。

【问题讨论】:

  • __new__ 不是静态方法;这是一个特殊情况的 class 方法,因此您不必用@classmethod 装饰它。
  • 我想我不明白细微差别,但我相信你是对的@chepner。这就是我想要达到的,或多或少。
  • 类方法接收调用类作为它的第一个参数;静态方法没有。

标签: python python-3.x metaprogramming metaclass


【解决方案1】:

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

元类的工作与类完全一样,因为当您调用它们时,它们会生成一个对象。类和元类都是工厂。区别在于元类在被调用时产生一个类对象,一个类在被调用时产生一个实例。

类和元类都定义了一个默认的__call__ 实现,基本上就是这样:

  1. 调用self.__new__ 生成一个新对象。
  2. 如果该新对象是 self 的实例/具有 this 的类 元类,然后也在该对象上调用__init__。

您生成了自己的 __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__

【讨论】:

  • 最后你的答案输出真正揭示了正在发生的事情,并得到你其他答案的支持。谢谢!
【解决方案2】:

在元类的__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__

【讨论】:

  • 你明白了; type.__call__() 首先调用__new__(),然后在__new__() 返回类的实例时调用__init__()。见docs.python.org/3/reference/datamodel.html#object.__new__
  • 谢谢!这正是我眼前问题的症结所在。我希望可以选择两个答案,因为 Martin 回答了更多问题,因为您的回答确实帮助我快速解决了我的问题。
猜你喜欢
  • 1970-01-01
  • 2014-02-16
  • 1970-01-01
  • 2020-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-03
  • 1970-01-01
相关资源
最近更新 更多