# 类机制与字节码

# compile 手动编译类

# compile(text,'','exec')

>>> text = '''
... class Dog:
...     def yelp(self):
...         print('woof')
... '''
>>> code = compile(text, '', 'exec')

>>> import dis
>>> dis.dis(code)
  2           0 LOAD_BUILD_CLASS
              2 LOAD_CONST               0 (<code object Dog at 0x10110f8a0, file "", line 2>)
              4 LOAD_CONST               1 ('Dog')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               1 ('Dog')
             10 CALL_FUNCTION            2
             12 STORE_NAME               0 (Dog)
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE

Disassembly of <code object Dog at 0x10110f8a0, file "", line 2>:
  2           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('Dog')
              6 STORE_NAME               2 (__qualname__)

  3           8 LOAD_CONST               1 (<code object yelp at 0x10110f390, file "", line 3>)
             10 LOAD_CONST               2 ('Dog.yelp')
             12 MAKE_FUNCTION            0
             14 STORE_NAME               3 (yelp)
             16 LOAD_CONST               3 (None)
             18 RETURN_VALUE

Disassembly of <code object yelp at 0x10110f390, file "", line 3>:
  4           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('woof')
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

image

image

# 虚拟机执行过程

# 创建名为 Dog 的函数对象

以上面手动编译的 Dog 类为例。

模块代码执行时,虚拟机内部有一个 栈帧 对象,维护着代码对象运行时时的上下文信息,全局和局部名字空间均指向模块属性空间。

前 3 条字节码,负责往运行栈加载数据。LOAD_BUILD_CLASS​ ​这个字节码源码在 Python/ceval.c​​ 中,其作用很简单,只是将 __build_class__​ ​这个内建函数加载进栈顶。该函数为 builtins​ ​模块中的一员,一个负责创建类的工具函数。

接着,第 2 条字节码将下标为 0 的常量加载到栈顶,这是代表 Dog 类代码块的代码对象,如红色箭头所示。第 3 条字节码将常量 Dog 加载到栈顶。第 4 条字码时我们在函数机制中学过的 MAKE_FUNCTION​​​指令,用于创建函数对象。

image

# 名为 Dog 的函数对象创建

指令执行完毕后,名为 Dog 的函数对象就被保存在栈顶了。

image

# __build_class__

# 执行前

查看 __build_class__​ ​函数帮助文档可以得知,该函数第一个参数是一个函数,第二个参数是一个名字。

>>> help(__build_class__)
Help on built-in function __build_class__ in module builtins:

__build_class__(...)
    __build_class__(func, name, *bases, metaclass=None, **kwds) -> class

    Internal helper function used by the class statement.

其中第一个函数由字节码 2-4 创建完成,即上一步创建的名为 Dog 的函数,第二个名字将在字节码 5 处加载入栈帧对象的临时栈顶。

__build_class__​源码实现在Python/bltinmodule.c​,观察可以发现,其并没有直接调用新创建的Dog函数,而是先创建一个dict对象,作为新生类的属性空间,然后从Dog函数中取出全局名字空间和代码对象,最后新建一个栈帧对象并开始执行Dog类代码对象。

其中关键的关系如下:

  • 模块的属性空间=Dog函数的全局名字空间=栈帧对象的全局名字空间

    栈帧对象的f_global会继承父栈帧对象的f_global

  • Dog函数的代码对象=Dog类的代码对象

  • Dog函数的局部名字空间(__dict__​)=Dog类的属性空间

即实际上Dog函数只是作为打包参数的包袱,将代码对象、全局名字空间等参数作为一个整体进行传递

image

# 准备属性空间

代码先从全局名字空间取出模块名 __name__​,在局部名字空间中保存为 __module__​,然后将类名 Dog 保存到局部名字空间中的 __qualname__​。最后取出 yelp 函数的代码对象,完成 yelp 函数对象的创建工作。至此,新生类 Dog 的属性空间完成初始化,属性空间准备完成。

image

# 创建新生类

类属性空间准备完毕,__build_class__​接着调用 type 元类型对象完成 Dog 类的创建。type 需要的参数有 3个,分别是: 类名基类列表 (类继承)以及代表 类属性空间 的 dict 对象。

至此,Dog类创建完成

>>> help(type)
Help on class type in module builtins:

class type(object)
 |  type(object_or_name, bases, dict)
 |  type(object) -> the object's type
 |  type(name, bases, dict) -> a new type

image

# 收尾

模块代码对象最后的字节码将 Dog 类对象保存于模块属性空间

image

# Python模拟

# 模块代码对象
>>> code
<code object <module> at 0x10b42e8a0, file "", line 2>
# Dog 类代码对象
>>> code.co_consts[0]
<code object Dog at 0x10b42ac00, file "", line 2>
# Dog 类 yelp 函数代码对象
>>> code.co_consts[0].co_consts[1]
<code object yelp at 0x10b5136f0, file "", line 3>
# 新建一个dict对象作为类的属性空间
>>> attrs = {}
# 以类属性空间为局部名字空间,执行类代码对象,以此完成新类属性空间初始化
>>> exec(code.co_consts[0], globals(), attrs)
>>> attrs
{'__module__': '__main__', '__qualname__': 'Dog', 'yelp': <function Dog.yelp at 0x10b732e18>}
# 调用type函数完成类对象创建
>>> Dog = type('Dog', (), attrs)
>>> Dog
<class '__main__.Dog'>
# 使用
>>> dog = Dog()
>>> dog.yelp()
woof