我试图通过在简单的Python程序中通过ceval.c
中的解释器循环来跟踪不同的字节码/操作码,来加深对CPython解释器的理解。我正在使用bytecode
和opcode
表示同一件事。
我的python程序是这样的:
#filename: test.py
x = 2
y = 3
if x < y:
z = x
elif True:
z = y
else:
z = 100
我正在使用Python 2.7.8,并使用如下调试标志来构建它:
wget https://www.python.org/ftp/python/2.7.8/Python-2.7.8.tgz # download
tar xvf Python-2.7.8.tgz # extract
cd Python2.7.8
./configure --with-pydebug # build with debug flag
make -j # parallel make
我对从解释器循环
for(;;)
中的不同switch
的opcode
语句遍历ceval.c
循环感兴趣,从964行开始。我在该
for
循环开始后立即添加了这些行,以检查解释程序是否正在运行我的文件,如果是,则打印出opcode
。964 for (;;) {
965 if (strcmp(filename, "../test.py") == 0) {
966 printf("%d\n", opcode);
967 }
我得到的输出是(手动添加的注释以显示
opcode
的DEFINE
opcode.h
): $ ./python.exe ../test.py | cat -n
1 0 // STOP_CODE
2 90 // HAVE_ARGUMENT
3 90 // HAVE_ARGUMENT
4 101 // LOAD_NAME
5 101 // LOAD_NAME
6 101 // LOAD_NAME
7 90 // STORE_NAME
我希望使用12种不同的操作码,而不是7种,因为当我分解同一文件的字节码时,有12个字节码命令。
$ ./python.exe -m dis ../pytests/test.py | sed "/^$/d" | cat -n
1 1 0 LOAD_CONST 0 (2)
2 3 STORE_NAME 0 (x)
3 2 6 LOAD_CONST 1 (3)
4 9 STORE_NAME 1 (y)
5 4 12 LOAD_NAME 0 (x)
6 15 LOAD_NAME 1 (y)
7 18 COMPARE_OP 0 (<)
8 21 POP_JUMP_IF_FALSE 33
9 5 24 LOAD_NAME 0 (x)
10 27 STORE_NAME 2 (z)
11 30 JUMP_FORWARD 21 (to 54)
12 6 >> 33 LOAD_NAME
我关于CPython交互程序如何工作的思维模型和/或我记录不同操作码的方法是错误的,或者两者都是错误的。您能解释为什么我在
ceval.c
文件的输出和使用python -m dis
包时看到不同的操作码吗? 最佳答案
跟踪显示有问题,因为在正常情况下,您不会执行任何STOP_CODE
(值0),而这会中断执行。另外,HAVE_ARGUMENT
不是操作码。对于Python 2.7,操作码为STORE_NAME
。
至于值的差异,这在任何不是直线(基本块)代码的代码中都是可以预期的。而且您不是直线代码。在COMPARE <
后面跟随POP_JUMP_IF_FALSE
跳转。