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 协议》,转载必须注明作者和本文链接
推荐文章: