赞
踩
Python 是一种解释性语言(虽然该定义由于字节码编译器的存在而有所模糊),即不需要在运行前就编译成机器语言,而是在运行时才编译为机器语言。这意味着源文件可以直接运行而不必显式地创建可执行文件再运行。
概括地说,Python 脚本的执行可以简化概括为如下两个步骤:
下面,让我们以一个计算黄金分割的函数脚本为例,Python 脚本是如何被编译为字节码,字节码又是如何被运行的:
GOLD = 0.618
def get_golden_ratio(x):
"""计算黄金分割值"""
return GOLD * x
print(get_golden_ratio(3))
我们首先将上述 Python 代码存入字符串 script
,然后使用内置函数 compile
编译它,可以得到代码对象 code
。
>>> script = ("GOLD = 0.618\n"
... "def get_golden_ratio(x):\n"
... " return GOLD * x\n"
... "print(get_golden_ratio(3))")
>>> code = compile(script, "test.py", "exec")
>>> code
<code object <module> at 0x000002BCD0AE2290, file "test.py", line 1>
code
对象常用的属性及含义如下:
属性名称 | 属性含义 |
---|---|
co_filename | 创建 code 对象的文件名 |
co_firstlineno | Python 源代码中第一行的行号 |
co_name | code 对象的名称 |
co_code | 字符串形式的原始字节码 |
co_consts | 字节码中使用的常量元组 |
co_varnames | 参数名和局部变量的标识符组成的元组 |
co_names | 除参数和函数局部变量之外的标识符组成的元组 |
co_cellvars | 单元变量的标识符的元组(通过包含作用域引用) |
co_freevars | 自由变量的标识符组成的元组(通过函数闭包引用) |
co_stacksize | 需要虚拟机的堆栈空间 |
例如,我们可以通过 co_code
属性查看这段代码的字节码(基于 Python 3.10):
>>> code.co_code
b'd\x00Z\x00d\x01d\x02\x84\x00Z\x01e\x02e\x01d\x03\x83\x01\x83\x01\x01\x00d\x04S\x00'
>>> [ch for ch in code.co_code]
[100, 0, 90, 0, 100, 1, 100, 2, 132, 0, 90, 1, 101, 2, 101, 1, 100, 3, 131, 1, 131, 1, 1, 0, 100, 4, 83, 0]
在 Python 3.6 及以上,每条字节码指令包含 2 个字节(即上述列表中每 2 个 0 - 255 之间的整数构成一条字节码指令),第 1 个字节是字节码指令,第 2 个字节是字节码指令的参数,如果字节码指令没有参数则使用 0
占位。需要注意的是,字节码是 CPython 解释器的实现细节,不保证不会在 Python 版本之间添加、删除或更改字节码。
也可以通过 co_names
属性查看这段代码使用的所有标识符,或通过 co_consts
属性查看这段代码使用的所有常量:
>>> code.co_names
('GOLD', 'get_golden_ratio', 'print')
>>> code.co_consts
(0.618, <code object get_golden_ratio at 0x000002BCD0AE3E10, file "test.py", line 2>, 'get_golden_ratio', 3, None)
通过这段代码使用的所有常量可以发现,函数 get_gold_ratio
被编译为另一个 code
对象,在这里被作为常量被引用。因此,我们可以进一步查看 get_gold_ratio
函数对应的字节码(基于 Python 3.10):
>>> code.co_consts[1].co_code
b't\x00|\x00\x14\x00S\x00'
>>> [ch for ch in code.co_consts[1].co_code]
[116, 0, 124, 0, 20, 0, 83, 0]
Python 虚拟机是一台完全通过软件定义的计算机,可执行字节码编译器所生成的字节码。
我们除通过 co_code
属性通过字符串形式的原始字节码分析 Python 虚拟机中的执行过程外,也可以结合标准库 dis
,反编译上述 Python 代码,分析字节码在 Python 虚拟机中的执行过程(基于 Python 3.10):
>>> import dis
>>> dis.dis(script)
1 0 LOAD_CONST 0 (0.618)
2 STORE_NAME 0 (GOLD)
2 4 LOAD_CONST 1 (<code object get_golden_ratio at 0x000002BCBEECFAA0, file "<dis>", line 2>)
6 LOAD_CONST 2 ('get_golden_ratio')
8 MAKE_FUNCTION 0
10 STORE_NAME 1 (get_golden_ratio)
4 12 LOAD_NAME 2 (print)
14 LOAD_NAME 1 (get_golden_ratio)
16 LOAD_CONST 3 (3)
18 CALL_FUNCTION 1
20 CALL_FUNCTION 1
22 POP_TOP
24 LOAD_CONST 4 (None)
26 RETURN_VALUE
Disassembly of <code object get_golden_ratio at 0x000002BCBEECFAA0, file "<dis>", line 2>:
3 0 LOAD_GLOBAL 0 (GOLD)
2 LOAD_FAST 0 (x)
4 BINARY_MULTIPLY
6 RETURN_VALUE
按引用顺序自下而上,首先说明第 3 行(return GOLD * x
)对应字节码的含义:
LOAD_GLOBAL(116), 0
:读取上一层 code
对象的 co_names
中的第 0 个标识符(GOLD
)的引用,并推入栈顶;此时栈中有 1 个元素;LOAD_FAST(124), 0
:读取当前 code
对象的 co_varnames
中的第 0 个标识符(x
)的引用,并推入栈顶;此时栈中有 2 个元素;BINARY_MULTIPLY(20), 0
:连续出栈两个栈顶元素(分别是 GOLD
和 x
的引用),执行 *
运算符,并将结果推入栈顶;此时栈中有 1 个元素;RETURN_VALUE(83), 0
:出栈栈顶元素,返回给调用者;此时栈中没有元素,第 3 行结束。第 1 行(GOLD = 0.618
)对应字节码的含义:
LOAD_CONST(100), 0
:读取当前 code
对象的 co_consts
中第 0 个值(0.618
)的引用,并推入栈顶;此时栈中有 1 个元素;STORE_NAME(90), 0
:出栈栈顶元素(0.618
的引用),并赋值给当前 code
对象的 co_names
中第 0 个标识符(GOLD
);此时栈中没有元素,第 1 行结束。第 2 行(def get_golden_ratio(x):
)对应字节码的含义:
LOAD_CONST(100), 1
:读取当前 code
对象的 co_consts
中第 1 个值(get_golden_ratio
的 code
对象)的引用;此时栈中有 1 个元素;LOAD_CONST(100), 2
:读取当前 code
对象的 co_consts
中第 2 个值(字符串 "get_golden_ratio"
)的引用;此时栈中有 2 个元素;MAKE_FUNCTION(132), 0
:出栈栈顶元素(字符串 "get_golden_ratio"
的引用)作为函数的名称;接着出栈栈顶元素(get_golden_ratio
的 code
对象的引用)作为函数关联的代码;构造新函数对象并推入栈顶;此时栈中有 1 个元素;STORE_NAME(90), 1
:出栈栈顶元素(新构造的 get_golden_ratio
函数代码对象),并赋值给当前 code
对象的 co_names
中第 1 个标识符(get_golden_ratio
);此时栈中没有元素,第 2 行结束。第 4 行(print(get_golden_ratio(3))
)对应字节码的含义:
LOAD_NAME(101), 2
:读取当前 code
对象的 co_names
中第 2 个标识符(内置函数 print
的代码对象)的引用,并推入栈顶;此时栈中有 1 个元素;LOAD_NAME(101), 1
:读取当前 code
对象的 co_names
中第 1 个标识符(get_golden_ratio
函数代码对象)的引用,并推入栈顶;此时栈中有 2 个元素;LOAD_CONST(100), 3
:读取当前 code
对象的 co_consts
中第 3 个值(整数 3
)的引用,并推入栈顶;此时栈中有 3 个元素;CALL_FUNCTION(131), 1
:出栈 1 个栈顶元素(整数 3
的引用)作为函数的参数;接着出栈栈顶元素(get_golden_ratio
函数代码对象)作为被调用的可调用对象;然后附带着参数调用该可调用函数,将可调用对象所返回的返回值(get_golden_ratio
函数的返回值)推入栈顶;此时栈中有 2 个元素;CALL_FUNCTION(131), 1
:出栈 1 个栈顶元素(get_golden_ratio
函数的返回值的引用)作为函数的参数,接着出栈栈顶元素(内置函数 print
的代码对象)作为被调用的可调用对象;然后附带着参数调用该可调用函数,将可调用对象所返回的返回值(内置函数 print
的返回值 None
)推入栈顶;此时栈中有 1 个元素;POP_TOP(1), 0
:出栈栈顶元素并删除;此时栈中没有元素;LOAD_CONST(100), 4
:读取当前 code
对象的 co_consts
中第 4 个值(None
)的引用,并推入栈顶;此时栈中有 1 个元素;RETURN_VALUE(83), 0
:出栈栈顶元素,返回给调用者;此时栈中没有元素,第 4 行结束。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。