Python 元类行为(不调用 __new__),有解释吗?

Posted

技术标签:

【中文标题】Python 元类行为(不调用 __new__),有解释吗?【英文标题】:Python metaclass behavior (not calling __new__), is there an explanation? 【发布时间】:2015-04-12 12:12:58 【问题描述】:

在一个名为 exp.py(如下)的文件中,我试图理解 Python 中的元类。似乎当元类的__new__ 方法使用“类型”构造函数来构造一个类时,它的__new__ 方法不会被使用它作为元类的类的子类调用:

class A(type):
    def __new__(cls, name, bases, dct):
        print "cls is:   ", cls
        print "name is:  ", name
        print "bases is: ", bases
        print "dct is:   ", dct
        print
        return super(A, cls).__new__(cls, name, bases, dct)

class B(object):
    __metaclass__ = A

class C(B): pass

class D(type):
    def __new__(cls, name, bases, dct):
        print "cls is:   ", cls
        print "name is:  ", name
        print "bases is: ", bases
        print "dct is:   ", dct
        return type(name, bases, dct)

class E(object):
    __metaclass__ = D

class F(E): pass

在终端中:

>>> from exp import *
cls is:    <class 'exp.A'>
name is:   B
bases is:  (<type 'object'>,)
dct is:    'count': 0, '__module__': 'exp', '__metaclass__': <class 'exp.A'>, '__init__': <function __init__ at 0x107eb9578>

cls is:    <class 'exp.A'>
name is:   C
bases is:  (<class 'exp.B'>,)
dct is:    '__module__': 'exp'

cls is:    <class 'exp.D'>
name is:   E
bases is:  (<type 'object'>,)
dct is:    'count': 0, '__module__': 'exp', '__metaclass__': <class 'exp.D'>, '__init__': <function __init__ at 0x107ebdb18>
>>> 

如您所见,元类 D 的 __new__ 方法在加载类 F 的定义时不会被调用。 (如果它被调用了,那么关于 F 类的信息也会被打印出来。)

有人可以帮我解释一下吗?


相关帖子:我在看What is a metaclass in Python?,好像在这句话中遇到了类似的东西,“注意这里__metaclass__属性不会被继承,父类(Bar.__class__)的元类会是. 如果 Bar 使用 __metaclass__ 属性创建 Bar 与 type()(而不是 type.__new__()),子类将不会继承该行为。但我并没有完全理解这一点。

【问题讨论】:

【参考方案1】:

在第二种情况下,实际上不是返回元类的实例,而是返回type 的实例,这反过来又将新创建的类E__class__ 属性设置为&lt;type 'type'&gt;,而不是D。因此as per the rule 2 Python 检查基类的__class__ 属性(或type(E)E.__class__)并决定使用type 作为F 的元类,因此永远不会调用D__new__在这种情况下。

如果存在dict['__metaclass__'],则使用它。 否则,如果至少有一个基类,则使用其元类(首先查找__class__ 属性,如果未找到,则使用其类型)。

所以,正确的方法是在元类的__new__方法中调用类型的__new__方法:

class D(type):
    def __new__(cls, name, bases, dct):
        print "cls is:   ", cls
        print "name is:  ", name
        print "bases is: ", bases
        print "dct is:   ", dct
        return type.__new__(cls, name, bases, dct)

class E(object):
    __metaclass__ = D

class F(E): pass

class A(object):
    pass

输出:

cls is:    <class '__main__.D'>
name is:   E
bases is:  (<type 'object'>,)
dct is:    '__module__': '__main__', '__metaclass__': <class '__main__.D'>
cls is:    <class '__main__.D'>
name is:   F
bases is:  (<class '__main__.E'>,)
dct is:    '__module__': '__main__'

【讨论】:

很好的答案,谢谢!顺便说一句,我对“(首先查找__class__ 属性,如果未找到,则使用其类型)”这句话有一些疑问。 1、什么情况下会找不到__class__属性? 2.在什么类型的情况下,类的__class__属性会与其类型不同(使用type()确定)?谢谢 @platypus 旧式类没有此属性,甚至type() 的输出也不同。对于新式类,默认类型是type,对于旧式类,默认类型是classobj。 New-style and classic classes

以上是关于Python 元类行为(不调用 __new__),有解释吗?的主要内容,如果未能解决你的问题,请参考以下文章

具有自定义元类行为的 Python 元类

元类中 __new__ 的行为(也在继承的上下文中)

在啥情况下调用来自父类的多个元类?

python:元类与抽象基类

python3元类的调用顺序

9.python 共享引用与单例