当我这样做时
list(some_function.__code__.co_code)
我可以看到该函数的实际字节码(以 list[int] 格式)。我发现有很多零——实际上比早期版本的 python 还要多。是的,我见过 this 问题,但是如果我在 python 3.12 中创建该函数,字节码中会有更多零。我的问题是:“所有这些零意味着什么?如果我想编写字节码,我如何计算需要多少个零?”
看看会发生什么:
def f(x):
return x + x/3
bytecode = list(f.__code__.co_code)
print(bytecode)
打印:
[151, 0, 124, 0, 100, 1, 124, 0, 122, 11, 0, 0, 122, 0, 0, 0, 83, 0]
def f(x):
return x + x/3
dis.dis(f, show_caches=True)
给出:
1 0 RESUME 0
2 2 LOAD_FAST 0 (x)
4 LOAD_FAST 0 (x)
6 LOAD_CONST 1 (3)
8 BINARY_OP 11 (/)
10 CACHE 0 (counter: 0)
12 BINARY_OP 0 (+)
14 CACHE 0 (counter: 0)
16 RETURN_VALUE
这与已经提到的代码有几点不同:
RESUME
,这是为了调试目的,如官方dis参考中提到的这是怎么回事?为什么使用所有操作码一个参数(也是操作码小于
dis.HAVE_ARGUMENT
的那个)
这并不是“超级”奇怪,但在处理以下函数时却变得有些奇怪:
def f():
print("hello world!")
字节码:
[151, 0, 116, 1, 0, 0, 0, 0, 0, 0, 0, 0, 100, 1, 171, 1, 0, 0, 0, 0, 0, 0, 1, 0, 121, 0]
有人也可以解释一下所有这些零吗?
提前致谢!
编辑我看到所有的零都是 CACHE 操作码,但是如何计算需要多少个 CACHE?
编辑有人建议字节码中的
0
是参数而不是 CACHE,但该断言似乎不正确。
查看带注释的输出:def f():
print("hello world!")
print(list(f.__code__.co_code))
for instr in dis.Bytecode(f):
print(instr.opname, instr.opcode, instr.arg)
人们可以看到:
[151, 0, 116, 1, 0, 0, 0, 0, 0, 0, 0, 0, 100, 1, 171, 1, 0, 0, 0, 0, 0, 0, 1, 0, 121, 0]
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
|--| |--|---------| | | | | | | | |
|--|---------------| | | | | | | | | |
| | | | | | | | | |
RESUME 151 0 --| | | | | | | | | |
LOAD_GLOBAL 116 1 ----| | | | | | | | |
LOAD_CONST 100 1 -----------------------|--| | | | | | |
CALL 171 1 -------------------------------|--| | | | |
POP_TOP 1 None -------------------------------------------------------|--| | |
RETURN_CONST 121 0 ---------------------------------------------------------------|--|
循环未指向的许多
0
值被
dis.dis(f, show_caches=True)
指示为“CACHE”
有关编写字节码并将其转换为字节码文件的信息,请参阅
xasm。这是在 PyPI 上的,但我已经有一段时间没有发布新版本了。因此从 github 上的源代码构建。 要了解字节码如何交互工作,请参阅
x-python。关于 PyPI 和新版本的情况也是如此。对于 x-python,甚至还有一个调试器可以让您执行单步指令。这就是所谓的 trepan-xpy。这可能是最难安装的,因为有很多依赖项,例如 trepan 调试器。 我将于 2024 年 4 月中旬在
BlackHat Asia进行演讲,因此可能会在此之前的某个时间发布新版本。 在我尝试回答你的问题之前,让我先举一个类似的情况。假设我正在尝试学习数字。我看到有所有这些不同风格的基本系统(类似于 Python 字节码版本)。有人问为什么二进制版本的数字比 10 进制版本的数字有更多的零?嗯,这是因为可供选择的数字较少,因此每个数字出现的频率更高。
在字节码中,您会看到很多零,因为零是最小的整数,并且指令的许多操作数是某种表的索引,例如常量元组、变量名称元组、 tab 。如果元组不为空,则它将有 0 个项目。由于元组出于某些需要,第 0 项可能会有一条操作数为 0 的指令。
正如之前提到的 0,在字节码中也用作没有值时的占位符。因此,没有操作数的指令通常会在其中放入 0。我不知道这是否是严格要求的。有人可能会检查其他值是否有效,Python 解释器会忽略它,就像它忽略值为 0 的操作数一样。