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

时间:2017-07-03 04:18:27

标签: python recursion disassembly

我一直在使用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 

What’s New In Python 3.7 文章记下了这一点:

  

dis()函数现在能够反汇编嵌套代码对象(理解代码,生成器表达式和嵌套函数,以及用于构建嵌套类的代码)。 (由Serhiy Storchaka在bpo-11822中提供。)

然而,除了Python 3.7尚未正式发布之外,如果您不想或不能使用Python 3.7,该怎么办?有没有办法在早期版本的Python中使用旧的dis.dis()

来实现这一点,例如3.5或2.7

2 个答案:

答案 0 :(得分:1)

首先,如果除了交互式使用之外你需要这个,我建议只是从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行如果你传递了内存中不存在的内容(例如,因为它已被垃圾收集),会对解释器进行分段。其他一些解决方案来自the above referenced question可能会更好(我尝试了gc但它似乎无法找到code个对象)。

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

答案 1 :(得分:1)

您可以执行以下操作(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__)