我一直在使用
dis
模块来观察 CPython 字节码。但最近,我注意到dis.dis()
有一些不方便的行为。
以这个例子为例:我首先定义一个函数
multiplier
,里面有一个嵌套函数inner
:
>>> def multiplier(n):
def inner(multiplicand):
return multiplicand * n
return inner
>>>
然后我用
dis.dis()
来拆解它:
>>> from dis import dis
>>> dis(multiplier)
2 0 LOAD_CLOSURE 0 (n)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object inner at 0x7ff6a31d84b0, file "<pyshell#12>", line 2>)
9 LOAD_CONST 2 ('multiplier.<locals>.inner')
12 MAKE_CLOSURE 0
15 STORE_FAST 1 (inner)
4 18 LOAD_FAST 1 (inner)
21 RETURN_VALUE
>>>
如您所见,它很好地反汇编了顶级代码对象。不过,它并没有拆解
inner
。它只是表明它创建了一个名为 inner
的代码对象,并显示代码对象的默认(无信息)__repr__()
。
有没有办法让
dis.dis()
递归地打印代码对象?也就是说,如果我有嵌套的代码对象,它将打印出所有代码对象的字节码,而不是在顶级代码对象处停止。我主要喜欢这个功能用于装饰器、闭包或生成器理解等。
看来最新版本的 Python - 3.7 alpha 1 - 完全具有我想要的行为 dis.dis()
:
>>> def func(a):
def ifunc(b):
return b + 10
return ifunc
>>> dis(func)
2 0 LOAD_CONST 1 (<code object ifunc at 0x7f199855ac90, file "python", line 2>)
2 LOAD_CONST 2 ('func.<locals>.ifunc')
4 MAKE_FUNCTION 0
6 STORE_FAST 1 (ifunc)
4 8 LOAD_FAST 1 (ifunc)
10 RETURN_VALUE
Disassembly of <code object ifunc at 0x7f199855ac90, file "python", line 2>:
3 0 LOAD_FAST 0 (b)
2 LOAD_CONST 1 (10)
4 BINARY_ADD
6 RETURN_VALUE
Python 3.7 的新增功能 文章记录了这一点:
dis()
函数现在能够反汇编嵌套代码对象(推导式、生成器表达式和嵌套函数的代码,以及用于构建嵌套类的代码)。 (由 Serhiy Storchaka 在 bpo-11822 中贡献。)
但是,除了Python 3.7还没有正式发布之外,如果你不想或者不能使用Python 3.7怎么办?有没有办法在早期版本的 Python(例如 3.5 或 2.7)中使用旧的
dis.dis()
来完成此任务?
import dis
def recursive_dis(code):
print(code)
dis.dis(code)
for obj in code.co_consts:
if isinstance(obj, type(code)):
print()
recursive_dis(obj)
f.__code__
来调用它,而不仅仅是
f
。例如:def multiplier(n):
def inner(multiplicand):
return multiplicand * n
return inner
recursive_dis(multiplier.__code__)
对于交互式使用,一个想法是使用其中一种方法来
通过对象的内存值访问对象来通过其内存地址获取代码对象,该地址打印在dis
输出中。
例如:
>>> def func(a):
... def ifunc(b):
... return b + 10
... return ifunc
>>> import dis
>>> dis.dis(func)
2 0 LOAD_CONST 1 (<code object ifunc at 0x10cabda50, file "<stdin>", line 2>)
3 LOAD_CONST 2 ('func.<locals>.ifunc')
6 MAKE_FUNCTION 0
9 STORE_FAST 1 (ifunc)
4 12 LOAD_FAST 1 (ifunc)
15 RETURN_VALUE
这里我复制粘贴上面打印的代码对象的内存地址
>>> import ctypes
>>> c = ctypes.cast(0x10cabda50, ctypes.py_object).value
>>> dis.dis(c)
3 0 LOAD_FAST 0 (b)
3 LOAD_CONST 1 (10)
6 BINARY_ADD
7 RETURN_VALUE
ctypes.cast
行将使解释器出现段错误。
上述引用的问题中的一些其他解决方案可能有效更好(我尝试了
gc
,但它似乎无法找到code
对象)。 这也意味着,如果您传递
dis
一个字符串,则此不会工作,因为当您尝试访问内部代码对象时,它们已经被垃圾收集了。您需要向其传递一个真正的 Python 对象,或者,如果您有一个字符串,请先
compile()
它。