用户自定义class
在本章中,我们将研究对用户自定义class的剖析,在demo1.py中,我们将研究单个class的实现,所以在这里并没有关于继承及多态的讨论。然而在demo1.py中,我们看到了许多类的内容,其中包括类的定义、类的构造函数、对象的实例化、类成员函数的调用等
demo1.py
class A(object):
name = "Python"
def __init__(self):
print("A::__init__")
def f(self):
print("A::f")
def g(self, aValue):
self.value = aValue
print(self.value)
a = A()
a.f()
a.g(10)
我们都知道,对于一个包含函数定义的Python源文件,在Python源文件编译后,会得到一个与源文件对应的PyCodeObject对象A,而与函数对应的PyCodeObject对象B则存储在A的co_consts变量中。那么对于包含类的Python源文件,编译之后的结果又如何呢?
>>> source = open("demo1.py").read()
>>> co = compile(source, "demo1.py", "exec")
>>> co.co_consts
('A', , 10, None)
>>> A_co = co.co_consts[1]
>>> A_co.co_consts
('Python', ,
,
)
>>> A_co.co_names
('__name__', '__module__', 'name', '__init__', 'f', 'g')
可以看到,class A会编译成一个PyCodeObject,存放在源文件code的co_consts变量中,而class A的函数也会编译成PyCodeObject,存放在对A对应的PyCodeObject中
class的动态元信息
所谓的class的元信息就是指关于class的信息,比如说class的名称,它所拥有的属性、方法、该class实例化时要为实例对象申请的内存空间大小等。对于demo1.py中所定义的class A来说,我们必须要知道这样的信息:class A中,有一个符号f,这个f对应了一个函数,还有一个符号g,也对应一个函数。有了这些关于A的元信息,才能创建A的class对象。元信息在编程语言中是一个非常重要的概念,正是有了这个东西,Java、C#的一些初级的诸如反射(Reflection)等动态特性才有可能得到实现。在以后的剖析中可以看到,Python中的元信息概念被发挥到淋漓尽致,因而Python也提供了Java、C#等语言所没有的高度灵活的动态性
现在,我们可以解释一下demo1.py所对应的字节码,看一下关于class A的字节码是长什么样的?
>>> source = open("demo1.py").read()
>>> co = compile(source, "demo1.py", "exec")
>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 ('A')
3 LOAD_NAME 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 1 ()
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME 1 (A)
15 22 LOAD_NAME 1 (A)
25 CALL_FUNCTION 0
28 STORE_NAME 2 (a)
16 31 LOAD_NAME 2 (a)
34 LOAD_ATTR 3 (f)
37 CALL_FUNCTION 0
40 POP_TOP
17 41 LOAD_NAME 2 (a)
44 LOAD_ATTR 4 (g)
47 LOAD_CONST 2 (10)
50 CALL_FUNCTION 1
53 POP_TOP
54 LOAD_CONST 3 (None)
57 RETURN_VALUE
我们单独把class A相关的字节码指令提取出来
0 LOAD_CONST 0 ('A')
3 LOAD_NAME 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 1 ()
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME 1 (A)
现在,我们可以开始分析class A是如何执行的了:
首先执行"0 LOAD_CONST 0"指令将类A的名称压入到运行时栈中,而接下来的LOAD_NAME指令和BUILD_TUPPLE指令是一个非常关键的点,这两条指令将基于类A的所有基类创建一个基类列表,当然这里只有一个名为object的基类。随后,Python虚拟机通过"9 LOAD_CONST 1"指令将与A对应的PyCodeObject压入到运行时栈中,并通过MAKE_FUNCTION指令创建一个PyFunctiobObject对象。在这些操作完成之后,我们来看一看这时的运行时栈
图1-1 MAKE_FUNCTION指令完成后的运行时栈
之后,Python虚拟机开始执行"15 CALL_FUNCTION 0"指令。根据函数机制那一章的分析,我们知道调用CALL_FUNCTION会创建一个新的PyFrameObject对象,并开始执行这个PyFrameObject对象中所包含的字节码序列,很显然,这些字节码序列来自运行时栈中那个PyFunctiobObject对象。参考上面的描述,我们可以发现,这段字节码序列实际就是来自与A对应的PyCodeObject对象。换句话说,现在Python虚拟机所面对的目标从与demo1.py对应的字节码序列转换到与class A对应的字节码序列
>>> dis.dis(A_co)
1 0 LOAD_NAME 0 (__