Python函数背后的秘密(2)
函数对象是如何创建的?#
程序运行时,虚拟机看见函数定义 def 语句,它都干些啥呢?
还是举一个简单例子吧,我们将一段代码放在字符串 s 中,然后用编译函数将它编译为字节码,再进行反汇编,就可以看到它的汇编指令了。
import dis
s = """
def foo(x,y='默认值',*,z='仅限关键字'):
a = 1
"""
c = compile(s, "func", "exec")
dis.dis(c)
下面是用 Python3.10.1 运行的结果(不同版本的结果可能有差异):
2 0 LOAD_CONST 6 (('默认值',))
2 LOAD_CONST 1 ('仅限关键字')
4 LOAD_CONST 2 (('z',))
6 BUILD_CONST_KEY_MAP 1
8 LOAD_CONST 3 (<code object foo ...)
10 LOAD_CONST 4 ('foo')
12 MAKE_FUNCTION 3 (defaults, kwdefaults)
14 STORE_NAME 0 (foo)
16 LOAD_CONST 5 (None)
18 RETURN_VALUE
Disassembly of <code object foo at 0x00000263A4993940, file "func", line 2>:
3 0 LOAD_CONST 1 (1)
2 STORE_FAST 3 (a)
4 LOAD_CONST 0 (None)
6 RETURN_VALUE
反汇编后有两个代码对象,第一个代码对象是 s 编译后得到的模块级的代码对象 c,第二个代码对象是函数 foo 的代码对象。def 语句位于模块中,函数对象是在模块中创建的。
c.co_consts 属性指令会用到,我们来看看其样子:
from pprint import pprint
pprint(c.co_consts)
('默认值',
'仅限关键字',
('z',),
<code object foo at 0x00000263A4993940, file "func", line 2>,
'foo',
None,
('默认值',))
对于模块级的代码对象 c 来说,co_names 就是其局部变量名组成的元组:(‘foo’,)。
列出了这些属性,就比较容易看懂创建函数的这些个指令了:
LOAD_CONST 6 ((‘默认值’,))
把 co_consts [6] 即元组 (‘默认值’,) 压入栈
LOAD_CONST 1 (‘仅限关键字’)
把 co_consts [1] 即’仅限关键字’压入栈
LOAD_CONST 2 ((‘z’,))
把 co_consts [2] 即键元组 (‘z’,) 压入栈
BUILD_CONST_KEY_MAP 1
将栈顶的前两项弹出,构建一个字典并压入栈
LOAD_CONST 3 (<code object foo …>)
把 c.co_consts [6], 即函数 foo 的代码块对象压入栈
LOAD_CONST 4 (‘foo’)
把 c.co_consts [7],即函数名 foo 压入栈
MAKE_FUNCTION 3 (defaults, kwdefaults)
这条指令创建函数对象,看来秘密在这里:
- 弹出栈中的函数名。
- 弹出对应的 foo 代码块。
- 创建函数对象,创建时把全局名字空间传递进去:
- 为函数对象申请空间,创建函数对象。
- 然后设置函数对象的成员属性。
MAKE_FUNCTION 后面的标志(本指令为 3 即 00000011),表示在栈帧中,代码对象 foo 的下面依次存储的是:
- 仅限关键字构成的字典。
- 默认值元组,位置参数和仅限位置参数的默认值,调用时可以用位置实参、关键字实参值来覆盖它们。
经过上面这些指令,把函数参数封装进了函数对象中了。后面两句是例行的返回指令。
未完待续
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: