python编译成class_Python虚拟机类机制之自定义class(四)

本文详细解析了Python中用户自定义class的编译过程,包括编译成PyCodeObject,class的动态元信息,以及如何通过metaclass创建class对象。通过示例代码和字节码分析,揭示了Python虚拟机如何处理class定义,包括LOAD_CONST、MAKE_FUNCTION、BUILD_CLASS等指令,以及动态元信息和静态元信息的概念。
摘要由CSDN通过智能技术生成

用户自定义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对象。在这些操作完成之后,我们来看一看这时的运行时栈

b39eb62370fd11eb2d8a0b7a16e1bbcd.png

图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 (__

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值