Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ operation is being performed, so the intermediate analysis object isn't useful:
Added *file* parameter.


.. function:: dis(x=None, *, file=None)
.. function:: dis(x=None, *, file=None, recursive=False)

Disassemble the *x* object. *x* can denote either a module, a class, a
method, a function, a generator, a code object, a string of source code or
Expand All @@ -152,9 +152,16 @@ operation is being performed, so the intermediate analysis object isn't useful:
The disassembly is written as text to the supplied *file* argument if
provided and to ``sys.stdout`` otherwise.

If *recursive* is true, also recursively disassemble internal code objects
(the code of comprehensions, generator expressions and local functions, and
the code used for building local classes).

.. versionchanged:: 3.4
Added *file* parameter.

.. versionchanged:: 3.7
Added *recursive* parameter.


.. function:: distb(tb=None, *, file=None)

Expand All @@ -169,8 +176,8 @@ operation is being performed, so the intermediate analysis object isn't useful:
Added *file* parameter.


.. function:: disassemble(code, lasti=-1, *, file=None)
disco(code, lasti=-1, *, file=None)
.. function:: disassemble(code, lasti=-1, *, file=None, recursive=False)
disco(code, lasti=-1, *, file=None, recursive=False)

Disassemble a code object, indicating the last instruction if *lasti* was
provided. The output is divided in the following columns:
Expand All @@ -189,9 +196,16 @@ operation is being performed, so the intermediate analysis object isn't useful:
The disassembly is written as text to the supplied *file* argument if
provided and to ``sys.stdout`` otherwise.

If *recursive* is true, also recursively disassemble internal code objects
(the code of comprehensions, generator expressions and local functions, and
the code used for building local classes).

.. versionchanged:: 3.4
Added *file* parameter.

.. versionchanged:: 3.7
Added *recursive* parameter.


.. function:: get_instructions(x, *, first_line=None)

Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ contextlib
:func:`contextlib.asynccontextmanager` has been added. (Contributed by
Jelle Zijlstra in :issue:`29679`.)

dis
---

The :func:`~dis.dis` and :func:`~dis.disassemble` functions now are able to
disassemble internal code objects (the code of comprehensions, generator
expressions and local functions, and the code used for building local
classes). (Contributed by Serhiy Storchaka in :issue:`11822`.)

distutils
---------

Expand Down
23 changes: 15 additions & 8 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def _try_compile(source, name):
c = compile(source, name, 'exec')
return c

def dis(x=None, *, file=None):
def dis(x=None, *, file=None, recursive=False):
"""Disassemble classes, methods, functions, generators, or code.

With no argument, disassemble the last traceback.
Expand All @@ -52,16 +52,16 @@ def dis(x=None, *, file=None):
if isinstance(x1, _have_code):
print("Disassembly of %s:" % name, file=file)
try:
dis(x1, file=file)
dis(x1, file=file, recursive=recursive)
except TypeError as msg:
print("Sorry:", msg, file=file)
print(file=file)
elif hasattr(x, 'co_code'): # Code object
disassemble(x, file=file)
disassemble(x, file=file, recursive=recursive)
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
_disassemble_bytes(x, file=file)
elif isinstance(x, str): # Source code
_disassemble_str(x, file=file)
_disassemble_str(x, file=file, recursive=recursive)
else:
raise TypeError("don't know how to disassemble %s objects" %
type(x).__name__)
Expand Down Expand Up @@ -331,12 +331,18 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
arg, argval, argrepr,
offset, starts_line, is_jump_target)

def disassemble(co, lasti=-1, *, file=None):
def disassemble(co, lasti=-1, *, file=None, recursive=False):
"""Disassemble a code object."""
cell_names = co.co_cellvars + co.co_freevars
linestarts = dict(findlinestarts(co))
_disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names,
co.co_consts, cell_names, linestarts, file=file)
if recursive:
for x in co.co_consts:
if hasattr(x, 'co_code'):
print(file=file)
print("Disassembly of %r:" % (x,), file=file)
disassemble(x, file=file, recursive=True)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the potential to get very noisy if disassembling a whole module, so I've posted a design question back on the original issue: https://bugs.python.org/issue11822#msg291718

I'd prefer to keep that discussion on the issue tracker, so I won't duplicate it here.


def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
constants=None, cells=None, linestarts=None,
Expand Down Expand Up @@ -368,9 +374,9 @@ def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
print(instr._disassemble(lineno_width, is_current_instr, offset_width),
file=file)

def _disassemble_str(source, *, file=None):
def _disassemble_str(source, **kwargs):
"""Compile the source string, then disassemble the code object."""
disassemble(_try_compile(source, '<dis>'), file=file)
disassemble(_try_compile(source, '<dis>'), **kwargs)

disco = disassemble # XXX For backwards compatibility

Expand Down Expand Up @@ -495,12 +501,13 @@ def _test():
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--recursive', action='store_true')
parser.add_argument('infile', type=argparse.FileType(), nargs='?', default='-')
args = parser.parse_args()
with args.infile as infile:
source = infile.read()
code = compile(source, args.infile.name, "exec")
dis(code)
dis(code, recursive=args.recursive)

if __name__ == "__main__":
_test()
53 changes: 47 additions & 6 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,16 +331,47 @@ def _fstring(a, b, c, d):
def _g(x):
yield x

def _h(y):
def foo(x):
'''funcdoc'''
return x+y
return foo

dis_nested = """\
%3d 0 LOAD_CLOSURE 0 (y)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object foo at 0x..., file "%s", line %d>)
6 LOAD_CONST 2 ('_h.<locals>.foo')
8 MAKE_FUNCTION 8
10 STORE_FAST 1 (foo)

%3d 12 LOAD_FAST 1 (foo)
14 RETURN_VALUE

Disassembly of <code object foo at 0x..., file "%s", line %d>:
%3d 0 LOAD_FAST 0 (x)
2 LOAD_DEREF 0 (y)
4 BINARY_ADD
6 RETURN_VALUE
""" % (_h.__code__.co_firstlineno + 1,
__file__,
_h.__code__.co_firstlineno + 1,
_h.__code__.co_firstlineno + 4,
__file__,
_h.__code__.co_firstlineno + 1,
_h.__code__.co_firstlineno + 3,
)

class DisTests(unittest.TestCase):

def get_disassembly(self, func, lasti=-1, wrapper=True):
def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs):
# We want to test the default printing behaviour, not the file arg
output = io.StringIO()
with contextlib.redirect_stdout(output):
if wrapper:
dis.dis(func)
dis.dis(func, **kwargs)
else:
dis.disassemble(func, lasti)
dis.disassemble(func, lasti, **kwargs)
return output.getvalue()

def get_disassemble_as_string(self, func, lasti=-1):
Expand Down Expand Up @@ -502,15 +533,25 @@ def test_dis_traceback(self):
def test_dis_object(self):
self.assertRaises(TypeError, dis.dis, object())

def test_disassemble_recursive(self):
dis = self.get_disassembly(_h, recursive=True)
dis = self.strip_addresses(dis)
self.assertEqual(dis, dis_nested)

dis = self.get_disassembly(_h.__code__, wrapper=False, recursive=True)
dis = self.strip_addresses(dis)
self.assertEqual(dis, dis_nested)


class DisWithFileTests(DisTests):

# Run the tests again, using the file arg instead of print
def get_disassembly(self, func, lasti=-1, wrapper=True):
def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs):
output = io.StringIO()
if wrapper:
dis.dis(func, file=output)
dis.dis(func, file=output, **kwargs)
else:
dis.disassemble(func, lasti, file=output)
dis.disassemble(func, lasti, file=output, **kwargs)
return output.getvalue()


Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ Extension Modules
Library
-------

- bpo-11822: The dis.dis() and dis.disassemble() functions now are able to
disassemble internal code objects.

- bpo-16500: Allow registering at-fork handlers.

- bpo-30470: Deprecate invalid ctypes call protection on Windows. Patch by
Expand Down