有没有办法使 dis.dis() 递归打印代码对象?

Is there a way to make dis.dis() print code objects recursively?

我一直在使用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() 递归打印代码对象?也就是说,如果我有嵌套的代码对象,它将打印出 all 代码对象的字节码,而不是在顶级代码对象处停止。我主要喜欢装饰器、闭包或生成器推导等方面的此功能。

看来 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 

What’s New In Python 3.7 文章对此做了说明:

The dis() function now is able to disassemble nested code objects (the code of comprehensions, generator expressions and nested functions, and the code used for building nested classes). (Contributed by Serhiy Storchaka in bpo-11822.)

不过,除了Python3.7还没有正式发布,如果你不想或者不能用Python3.7怎么办?有没有办法在 Python 的早期版本(例如 3.5 或 2.7)中使用旧的 dis.dis() 来完成此操作?

首先,如果除了交互式使用之外,您还需要它,我建议您只从 Python 3.7 源代码复制代码并向后移植(希望这不难)。

对于交互式使用,一个想法是使用其中一种方法 access an object by its memory value 通过其内存地址获取代码对象,该地址打印在 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 行传递内存中不存在的内容(例如,因为它已被垃圾收集),ctypes.cast 行将导致解释器出现段错误。来自 the above referenced question 的其他一些解决方案可能效果更好(我尝试了 gc 一个,但它似乎无法找到 code 对象)。

这也意味着如果您传递 dis 一个字符串,这个 将不会 工作,因为在您尝试时内部代码对象已经被垃圾收集访问它们。你需要给它传递一个真正的 Python 对象,或者,如果你有一个字符串,首先 compile() 它。

你可以这样做 (Python 3):

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)

https://repl.it/@solly_ucko/Recursive-dis

请注意,您必须使用 f.__code__ 而不是 f 来调用它。例如:

def multiplier(n):
    def inner(multiplicand):
        return multiplicand * n
    return inner

recursive_dis(multiplier.__code__)