Python中关于元类(metaclass)的问题请教
class Meta1(type):
def __new__(meta, classname, supers, classdict):
print('In Meta1 new: ', classname, supers, classdict, sep='\n...')
return type.__new__(meta, classname, supers, classdict)
def __init__(Class, classname, supers, classdict):
print('In Meta1 init:', classname, supers, classdict, sep='\n...')
def __call__(meta, classname, supers, classdict):
print('In Meta1 call: ', classname, supers, classdict, sep='\n...')
return type.__call__(meta, classname, supers, classdict)
class Meta2(type, metaclass=Meta1):
def new(meta, classname, supers, classdict):
print(‘In Meta2 new: ‘, classname, supers, classdict, sep=’\n…’)
return type.new(meta, classname, supers, classdict)
def init(Class, classname, supers, classdict):
print(‘In Meta2 init:’, classname, supers, classdict, sep=’\n…’)
print(’…init class object:’, list(Class.dict.keys()))
def call(meta):
print(‘In Meta2 call’)
return type.call(meta)
class Eggs:
pass
print(‘making class’)
class Spam(Eggs, metaclass=Meta2):
data = 1
def meth(self, arg):
pass
print(‘making instance’)
X = Spam()
print(‘data:’, X.data)
上面代码输出如下:
In Meta1 new:
…Meta2
…(,)
…{‘module’: ‘main’, ‘qualname’: ‘Meta2’, ‘new’: , ‘init’: , ‘call’: }
In Meta1 init:
…Meta2
…(,)
…{‘module’: ‘main’, ‘qualname’: ‘Meta2’, ‘new’: , ‘init’: , ‘call’: }
making class
In Meta1 call:
…Spam
…(,)
…{‘module’: ‘main’, ‘qualname’: ‘Spam’, ‘data’: 1, ‘meth’: }
In Meta2 new:
…Spam
…(,)
…{‘module’: ‘main’, ‘qualname’: ‘Spam’, ‘data’: 1, ‘meth’: }
In Meta2 init:
…Spam
…(,)
…{‘module’: ‘main’, ‘qualname’: ‘Spam’, ‘data’: 1, ‘meth’: }
…init class object: [‘module’, ‘data’, ‘meth’, ‘doc’]
making instance
In Meta2 call
data: 1
我的问题如下:
1、Spam 的元类是 Meta2,Meta2 的元类是 Meta1。为何执行“ class Spam(Eggs, metaclass=Meta2)”定义 Spam 的时候,在对 Meta2 执行 new 和 init 做初始化之前,要先去执行 Meta1 的 call ?
2、执行“ X = Spam()”时,Meta1 和 Meta2 都定义了 call 方法的前提下,为何不是执行 Meta1 的 call,而是去执行 Meta2 的 call ?
Python中关于元类(metaclass)的问题请教
你先理解元类是类的类,元类的实例是类。
所以在定义 Meta2 时( class Meta2(type, metaclass=Meta1)),实际上是生成了一个 Meta1 的实例,就需要调用 Meta1 的 new 和 init。
而在定义 Spam 时( class Spam(Eggs, metaclass=Meta2)),需要生成一个 Meta2 的实例作为 Spam 类,也就是执行
Meta2()。Meta2 是个 callable 的对象,调用它实际上是调用 Meta2.call(…),这个调用又会被转变成 Meta1.call(Meta2, …)。而你在这个方法里调用了 type.call(),这个方法会负责去调用 Meta2.new() 和 Meta2.init()。
最后,Meta1.call() 是负责创建 Meta2 的对象的,Meta2.call() 是负责创建 Spam 的对象的。在执行 Spam() 时,Meta2 已经在定义 Spam 类时就被创建过了,创建 Spam 的对象时不需要再调用 Meta1.call() 创建新的 Meta2,而是直接使用已有的 Meta2 创建 Spam 的对象。
我无法理解你的问题
非常感谢您详细耐心的解答,关于您的说明,我还有两个疑问:
1、Meta2 因为声明了 call 方法,所以是个 callable 对象,定义 Spam 时会调用 Meta2.call。同样的,Meta1 也是个 callable 对象,那为何声明 Meta2 时不是去调用 Meta1.__call__而却是去调用 Meta1 的 new 和 init 方法呢?
2、声明 Spam 时调用了 Meta2.call,为何这个调用会被转变成 Meta1.call,这是基于什么原理呢?
万分感谢!
感谢指点,我先去学习一下在你个人网站上的文章。另外,调用元类的 type.__call__方法时等同于调用元类的子类的 new 和 init 方法。关于这一点我目前是硬记的,不知要如何去理解。
其实不需要理解,只是 Python 是这样实现的。
你去看它的 C 实现,调用时会先用 new 方法去生成一个对象,如果这个对象是目标类的实例,就再调用 init 方法,否则直接返回。其他面向对象的语言也需要某种机制去调用父类的构造函数,至于是 runtime 或编译器自动做的,还是手写,就看语言的设计者了。
实际使用中你根本不会有 override type.call 的需求,元类的 new 已经能干任何事了,它连签名都和 call 一样,只是不会自动调用 init 而已。
另外,type.call() 等效于 type()。
关于您说的这句话“ Meta2 是 callable,不是因为它定义了 call 方法,而是它的父类( Meta1 )定义了 call 方法”,我查了一些资料,builtin.py 中对 callable()的定义如下:<br>def callable(p_object): # real signature unknown; restored from __doc__<br> """<br> callable(object) -> bool<br><br> Return whether the object is callable (i.e., some kind of function).<br> Note that classes are callable, as are instances with a __call__() method.<br> """<br> return False<br><br>
这个定义是否可以理解为类都是 callable 的,而实例必须实现了__call__方法后才是 callable 的?
单从这个说明来看,并没有特意提到只有当元类定义了__call__方法后,元类的子类才是 callable 的吧?毕竟元类的子类本身就是类,所以本身就是 callable,而不需要通过元类来判断?
实现可以看 type_call 函数:
https://github.com/python/cpython/blob/master/Objects/typeobject.c
类在声明时定义了 call 方法后,它的实例才是 callable 的。
元类都继承自 type,type 定义了 call 方法。类是元类的实例,所以类都是 callable 的。元类也是 type 的实例,也是类,所以也是 callable。
谢谢,您看我以下的总结是否正确
1、type 类是 callable 的,这是一个基本定义,没有理由(并不是因为 type 类实现了__call__方法所以才 callable )。
2、因为所有其它类都是 type 类的实例,并且 type 类实现了__call__方法,所以其它类也默认实现了__call__方法,因此其它类都是 callable 的。所以类名()=类名.call()。
3、因为 Meta2 元类是 Meta1,声明 Meta2 的时候,执行到 class Meta2…这句的末尾时会调用 Meta1()生成一个 Meta1 实例。而执行 Meta1()等于分别执行 Meta1 的__new__和__init__,这就是声明 Meta2 时会执行 Meta1 的__new__和__init__的原因。
4、因为 Spam 元类是 Meta2,声明 Spam 的时候,执行到 class Spam…这句的末尾时会调用 Meta2()生成一个 Meta2 实例。
因为 Meta2 的元类是 Meta1,所以 Meta2 是 Meta1 的实例,即 Meta2=Meta1() => Meta2()=Meta1()()。而执行 Meta1()()等于依序执行 Meta1 的__new__、init__和__call。
因为之前声明 Meta2 的时候已经执行了 Meta1 的__new__和__init__,所以现在不会因为调用 Meta2()而重复执行 Meta1 的__new__和__init__,而是会直接执行 Meta1 的__call__。在执行 Meta1 的__call__时,因为 PYTHON 自身的机制,使得 type.call__语句会转而去执行 Meta1 实例(即 Meta2)的__new__和__init。
5、执行“ X=Spam()”生成 Spam 实例时,因为 Spam 的元类是 Meta2,所以 Spam=Meta2() => Spam()=Meta2()()。而执行 Meta2()()等于依序执行 Meta2 的__new__、init__和__call。
因为之前声明 Spam 的时候已经执行了 Meta2 的__new__和__init__,所以现在不会因为 Spam()而重复执行 Meta2 的__new__和__init__,而是会直接执行 Meta2 的__call__。在执行 Meta2 的__call__时,调用了 type.call(meta),值得注意的是,因为之前 Meta2()已经执行完毕,所以 Spam 对象已经生成,所以 type.__call__的参数 meta 其实就是 Spam 对象,而不是 Meta2 对象。
- callable 的检查是查看它的类型是否有 tp_call: x->ob_type->tp_call != NULL。
而 type 在它的 C 实现里,将 tp_call 设为了之前我提到的 type_call 函数。
而且 type 的类型也是 type,所以 type 的类型实现了 tp_call,因此它是 callable 的。
3. 生成的那个 Meta1 实例就是 Meta2。接着 type.call 调用了 Meta1 定义里的 new 和 init 对 Meta2 进行初始化。
4. 定义 Spam 类时,不会再调用 Meta1(),因为你传入的是 metaclass=Meta2,而不是 metaclass=Meta1(…)。
不是因为 Meta2 已经生成了,所以不需要重新生成,而是你传入的就是 Meta2 这个对象,而不是再传入一个新的 Meta1 类的实例。
5. 同上。
我觉得这个帖子可以成为"如何提问以及使用何种态度来沟通"的典范.论坛上的戾气太重了,要是都如楼上两位礼貌就好啦~~
人不知,而不愠,不亦君子乎?
与看到的诸君共勉~ ^_^


