python函数字节码中的零是什么意思?

时间:2014-08-01 15:40:20

标签: python bytecode

我正在尝试自学python字节码是如何工作的,所以我可以通过操作函数的代码来做一些事情(只是为了好玩而不是实际使用)所以我从一些简单的例子开始,例如:

def f(x):
    return x + 3/x

字节码是*:

(124, 0, 0, 100, 1, 0, 124, 0, 0, 20, 23, 83)

因此,124LOAD_FAST字节码,并且正在加载的对象的名称是f.__code__.co_varnames[0]0124之后的数字。 1}}。 100表示要LOAD_CONST加载f.__code__.co_consts[1],其中1100之后的数字。但是有一堆辅助零,比如第二个,第三个和第五个零,似乎没有任何意义,至少对我而言。它们表示什么?

文本字节码:

>>> dis.dis(f)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (3)
              6 LOAD_FAST                0 (x)
              9 BINARY_DIVIDE       
             10 BINARY_ADD          
             11 RETURN_VALUE   

*注意:在Python 3中(字节码可能与上面不同),可以通过以下方式找到字节码:

>>> list(f.__code__.co_code)
[124, 0, 100, 1, 124, 0, 27, 0, 23, 0, 83, 0]

2 个答案:

答案 0 :(得分:12)

大量字节码接受参数(任何字节码的代码点都在dis.HAVE_ARGUMENT或以上。那些具有2字节参数的字节码,以小端顺序。

您可以看到Python当前使用的字节码的定义及其在dis module documenation中的含义。

使用2个字节,您可以为任何字节码提供0到65535之间的参数值,对于字节码而不是更多,您可以前缀使用EXTENDED_ARG bytecode的字节码,再添加2个字节理论上你可以多次使用EXTENDED_ARG,但CPython解释器使用int作为oparg变量,因此实际用途仅限于4字节值

从Python 3.4开始,dis模块为您提供Instruction instances,使您可以更轻松地内省每个字节码及其参数。使用此功能,我们可以浏览您为函数f找到的字节代码:

>>> def f(x):
...     return x + 3/x
... 
>>> f.__code__.co_varnames
('x',)
>>> f.__code__.co_consts
(None, 3)
>>> import dis
>>> instructions = dis.get_instructions(f)
>>> instructions
<generator object _get_instructions_bytes at 0x10be77048>
>>> instruction = next(instructions)
>>> instruction
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='x', argrepr='x', offset=0, starts_line=2, is_jump_target=False)

所以第一个操作码,124或LOAD_FAST将第一个本地名称的值放在堆栈上;这是0 0参数,little-endian解释为整数0,是代码本地数组的索引。 dis已填写argval属性,向我们显示第一个本地名称为x。在上面的会话中,我将展示如何内省代码对象以查看名称列表。

>>> instruction = next(instructions)
>>> instruction
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=3, argrepr='3', offset=3, starts_line=None, is_jump_target=False)

下一条指令将常量推入堆栈;参数现在是1 0,或者是整数1的小尾数;与代码对象关联的第二个常量。 f.__code__.co_consts元组显示它是3,但Instruction对象也会将其作为argval属性。

>>> next(instructions)
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='x', argrepr='x', offset=6, starts_line=None, is_jump_target=False)

接下来我们有另一个LOAD_FAST,将另一个对本地名称x的引用推送到堆栈上。

>>> next(instructions)
Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=9, starts_line=None, is_jump_target=False)

这是一个没有参数的字节码 ,操作码27低于dis.HAVE_ARGUMENT。不需要参数,因为此操作码获取堆栈中的前两个值,将它们分开,将浮点结果推回堆栈。因此,最后x3常量被采用,划分并且结果会被推回。

>>> next(instructions)
Instruction(opname='BINARY_ADD', opcode=23, arg=None, argval=None, argrepr='', offset=10, starts_line=None, is_jump_target=False)

另一个无参数字节码;这个将前两个堆栈值相加,取而代之的是结果。取BINARY_TRUE_DIVIDE的结果,并首先推送x的值,然后将结果放回堆栈。

>>> next(instructions)
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=11, starts_line=None, is_jump_target=False)

最后一条指令,另一条不带参数的指令。 RETURN_VALUE结束当前帧,将结果中的最高值作为结果返回给调用者。

答案 1 :(得分:6)

在CPython 3.6之前,CPython字节码参数占用2个字节。额外的零是参数的高字节。