字节码实际上不会被解释为机器码,除非您正在使用一些特殊的实现,比如pypy。

除此之外,你的描述是正确的。字节码被加载到Python运行时中,并由一个虚拟机进行解释,虚拟机是一段代码,它读取字节码中的每个指令并执行指示的任何操作。您可以通过dis模块看到这个字节码,如下所示:>>> def fib(n): return n if n < 2 else fib(n - 2) + fib(n - 1)

...
>>> fib(10)
55
>>> import dis
>>> dis.dis(fib)
1 0 LOAD_FAST 0 (n)
3 LOAD_CONST 1 (2)
6 COMPARE_OP 0 (
9 JUMP_IF_FALSE 5 (to 17)
12 POP_TOP
13 LOAD_FAST 0 (n)
16 RETURN_VALUE
>> 17 POP_TOP
18 LOAD_GLOBAL 0 (fib)
21 LOAD_FAST 0 (n)
24 LOAD_CONST 1 (2)
27 BINARY_SUBTRACT
28 CALL_FUNCTION 1
31 LOAD_GLOBAL 0 (fib)
34 LOAD_FAST 0 (n)
37 LOAD_CONST 2 (1)
40 BINARY_SUBTRACT
41 CALL_FUNCTION 1
44 BINARY_ADD
45 RETURN_VALUE
>>>

详细说明

很重要的一点是要明白,上面的代码从来不是由CPU执行的;它也从来没有被转换成某种东西(至少,不是在Python的官方C实现上)。CPU执行虚拟机代码,虚拟机代码执行字节码指令指示的工作。当解释器想要执行fib函数时,它一次读取一条指令,并按照它们的指示执行。它查看第一条指令LOAD_FAST 0,从而从保存参数的位置获取参数0(传递给n的fib),并将其推送到解释器堆栈(Python的解释器是一个堆栈机)。在读取下一条指令LOAD_CONST 1时,它从函数拥有的常量集合(在本例中恰好是2)中获取常量1,并将其推送到堆栈上。你可以看到这些常数:>>> fib.func_code.co_consts

(None, 2, 1)

下一条指令COMPARE_OP 0告诉解释器弹出最上面的两个堆栈元素并在它们之间执行不等式比较,将布尔结果推回到堆栈上。第四条指令根据布尔值决定是向前跳五条指令还是继续下一条指令。所有这些措辞都解释了fib中条件表达式的if n < 2部分。对于您来说,梳理出字节码其余部分的含义和行为将是一个非常有指导意义的练习。唯一一个,我不确定是POP_TOP;我猜JUMP_IF_FALSE的定义是将其布尔参数留在堆栈上,而不是弹出它,所以必须显式弹出它。

更具指导意义的是检查原始字节码的fib,因此:>>> code = fib.func_code.co_code

>>> code
'|\x00\x00d\x01\x00j\x00\x00o\x05\x00\x01|\x00\x00S\x01t\x00\x00|\x00\x00d\x01\x00\x18\x83\x01\x00t\x00\x00|\x00\x00d\x02\x00\x18\x83\x01\x00\x17S'
>>> import opcode
>>> op = code[0]
>>> op
'|'
>>> op = ord(op)
>>> op
124
>>> opcode.opname[op]
'LOAD_FAST'
>>>

因此,您可以看到字节码的第一个字节是LOAD_FAST指令。下一对字节'\x00\x00'(16位中的数字0)是LOAD_FAST的参数,并告诉字节码解释器将参数0加载到堆栈中。