问题描述
我一直在使用 dis
模块观察CPython字节码。但是最近,我注意到 dis.dis()
的某些不便行为。
I've been using the dis
module to observe CPython bytecode. But lately, I've noticed some inconvenient behavior of dis.dis()
.
以这个例子为例:我首先定义一个函数乘数
,其中包含嵌套函数 inner
:
Take this example for instance: I first define a function multiplier
with a nested function inside of it 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 __()
As you can see, it disassembled the top-level code object fine. However, it did not disassemble inner
. It simply showed that it created a code object named inner
and displayed the default (uninformative) __repr__()
for code objects.
有没有办法让 dis.dis()
递归打印代码对象?也就是说,如果我有嵌套的代码对象,它将打印出 all 个代码对象的字节码,而不是停在顶级代码对象上。我主要希望此功能用于装饰器,闭包或生成器理解。
Is there a way I can make dis.dis()
print the code objects recursively? That is, if I have nested code objects, it will print the bytecode for all of the code objects out, rather than stopping at the top-level code object. I'd mainly like this feature for things such as decorators, closures, or generator comprehensions.
似乎是Python的最新版本--正是我想要的 dis.dis()
行为:
It appears that the latest version of Python - 3.7 alpha 1 - has exactly the behavior I want from 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
文章对此进行了说明:
The What’s New In Python 3.7 article makes note of this:
但是,除了Python 3.7尚未正式发布外,如果您不这样做,该怎么办?想要还是不能使用Python 3.7?是否可以使用旧的 dis.dis()
在Python的早期版本(例如3.5或2.7)中完成此操作?
However, besides Python 3.7 not being formally released yet, what if you don't want or cannot use Python 3.7? Are there ways to accomplish this in earlier versions of Python such as 3.5 or 2.7 using the old dis.dis()
?
推荐答案
首先,如果您需要将其用于交互用途以外的其他用途,我建议您仅从Python 3.7源中复制代码并反向移植(希望这并不困难) 。
First off, if you need this for anything other than interactive use, I would recommend just copying the code from the Python 3.7 sources and backporting it (hopefully that isn't difficult).
对于交互式使用,一个想法是使用一种方式,以通过其内存地址获取代码对象,该地址将显示在 dis
输出中。
For interactive use, an idea would be to use one of the ways to access an object by its memory value to grab the code object by its memory address, which is printed in the dis
output.
例如:
>>> 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
在这里我复制粘贴上面打印的代码对象的内存地址
Here I copy-paste the memory address of the code object printed above
>>> 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
一个问题,但似乎无法找到代码
对象)。
WARNING: the ctypes.cast
line will segfault the interpreter if you pass it something that doesn't exist in memory (say, because it's been garbage collected). Some of the other solutions from the above referenced question may work better (I tried the gc
one but it didn't seem to be able to find code
objects).
这也意味着,如果您传递 dis
字符串,则此无效 ,因为在您尝试访问内部代码对象时,它们已经被垃圾回收了。您需要将其传递给真实的Python对象,或者,如果您有字符串,则首先传递 compile()
。
This also means that this won't work if you pass dis
a string, because the internal code objects will already be garbage collected by the time you try to access them. You need to either pass it a real Python object, or, if you have a string, compile()
it first.
这篇关于有没有办法让dis.dis()递归打印代码对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!