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 ?
1
keakon 2017-08-26 14:21:51 +08:00
你先理解元类是类的类,元类的实例是类。
所以在定义 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 的对象。 |
2
saximi OP @keakon 非常感谢您详细耐心的解答,关于您的说明,我还有两个疑问:
1、Meta2 因为声明了 call 方法,所以是个 callable 对象,定义 Spam 时会调用 Meta2.__call__。同样的,Meta1 也是个 callable 对象,那为何声明 Meta2 时不是去调用 Meta1.__call__而却是去调用 Meta1 的 new 和 init 方法呢? 2、声明 Spam 时调用了 Meta2.__call__,为何这个调用会被转变成 Meta1.__call__,这是基于什么原理呢? 万分感谢! |
3
keakon 2017-08-27 01:19:39 +08:00 1
1. Meta2 是 callable,不是因为它定义了 __call__ 方法,而是它的父类( Meta1 )定义了 __call__ 方法。
另外,__new__ 和 __init__ 是由 type.__call__() 负责调用的。 2. 假设 obj 是 Cls 类的实例,obj() 等效于 obj.__call__(),等效于 Cls.__call__(obj)。 或者更通用点,obj.f(1) 等效于 Cls.f(obj, 1)。 这个语法实现在 Python 文档里有描述(搜「 Instance methods 」): https://docs.python.org/3/reference/datamodel.html 实现原理是用 descriptor,详细实现可以参考: https://www.keakon.net/2009/12/08/%E7%94%A8Descriptor%E5%AE%9E%E7%8E%B0%E5%8F%A6%E4%B8%80%E7%A7%8D%E9%A3%8E%E6%A0%BC%E7%9A%84Decorator |
4
saximi OP @keakon 感谢指点,我先去学习一下在你个人网站上的文章。另外,调用元类的 type.__call__方法时等同于调用元类的子类的 new 和 init 方法。关于这一点我目前是硬记的,不知要如何去理解。
|
5
keakon 2017-08-27 11:11:06 +08:00
其实不需要理解,只是 Python 是这样实现的。
你去看它的 C 实现,调用时会先用 __new__ 方法去生成一个对象,如果这个对象是目标类的实例,就再调用 __init__ 方法,否则直接返回。其他面向对象的语言也需要某种机制去调用父类的构造函数,至于是 runtime 或编译器自动做的,还是手写,就看语言的设计者了。 实际使用中你根本不会有 override type.__call__ 的需求,元类的 __new__ 已经能干任何事了,它连签名都和 __call__ 一样,只是不会自动调用 __init__ 而已。 另外,type.__call__() 等效于 type()。 |
6
saximi OP @keakon 关于您说的这句话“ Meta2 是 callable,不是因为它定义了 __call__ 方法,而是它的父类( Meta1 )定义了 __call__ 方法”,我查了一些资料,builtin.py 中对 callable()的定义如下:
``` def callable(p_object): # real signature unknown; restored from __doc__ """ callable(object) -> bool Return whether the object is callable (i.e., some kind of function). Note that classes are callable, as are instances with a __call__() method. """ return False ``` 这个定义是否可以理解为类都是 callable 的,而实例必须实现了__call__方法后才是 callable 的? 单从这个说明来看,并没有特意提到只有当元类定义了__call__方法后,元类的子类才是 callable 的吧?毕竟元类的子类本身就是类,所以本身就是 callable,而不需要通过元类来判断? |
7
keakon 2017-08-27 11:29:44 +08:00
实现可以看 type_call 函数:
https://github.com/python/cpython/blob/master/Objects/typeobject.c |
8
keakon 2017-08-27 11:37:20 +08:00
类在声明时定义了 __call__ 方法后,它的实例才是 callable 的。
元类都继承自 type,type 定义了 __call__ 方法。类是元类的实例,所以类都是 callable 的。元类也是 type 的实例,也是类,所以也是 callable。 |
9
saximi OP @keakon 谢谢,您看我以下的总结是否正确
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 对象。 |
10
keakon 2017-08-27 16:24:06 +08:00
1. 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. 同上。 |
11
rust 2017-08-27 21:42:42 +08:00
我觉得这个帖子可以成为"如何提问以及使用何种态度来沟通"的典范.论坛上的戾气太重了,要是都如楼上两位礼貌就好啦~~
人不知,而不愠,不亦君子乎? 与看到的诸君共勉~ ^_^ |