Issue 9147: Add dis.code_info()

This commit is contained in:
Nick Coghlan 2010-08-17 08:03:36 +00:00
parent 9887683f74
commit eae2da1da7
4 changed files with 224 additions and 38 deletions

View file

@ -36,6 +36,18 @@ the following command can be used to get the disassembly of :func:`myfunc`::
The :mod:`dis` module defines the following functions and constants: The :mod:`dis` module defines the following functions and constants:
.. function:: code_info(x=None)
Return a formatted multi-line string with detailed code object
information for the supplied function, method, source code string
or code object.
Note that the exact contents of code info strings are highly
implementation dependent and they may change arbitrarily across
Python VMs or Python releases.
.. versionadded:: 3.2
.. function:: dis(x=None) .. function:: dis(x=None)
Disassemble the *x* object. *x* can denote either a module, a Disassemble the *x* object. *x* can denote either a module, a

View file

@ -19,9 +19,6 @@ def _try_compile(source, name):
Utility function to accept strings in functions that otherwise Utility function to accept strings in functions that otherwise
expect code objects expect code objects
""" """
# ncoghlan: currently only used by dis(), but plan to add an
# equivalent for show_code() as well (but one that returns a
# string rather than printing directly to the console)
try: try:
c = compile(source, name, 'eval') c = compile(source, name, 'eval')
except SyntaxError: except SyntaxError:
@ -37,11 +34,11 @@ def dis(x=None):
if x is None: if x is None:
distb() distb()
return return
if hasattr(x, '__func__'): if hasattr(x, '__func__'): # Method
x = x.__func__ x = x.__func__
if hasattr(x, '__code__'): if hasattr(x, '__code__'): # Function
x = x.__code__ x = x.__code__
if hasattr(x, '__dict__'): if hasattr(x, '__dict__'): # Class or module
items = sorted(x.__dict__.items()) items = sorted(x.__dict__.items())
for name, x1 in items: for name, x1 in items:
if isinstance(x1, _have_code): if isinstance(x1, _have_code):
@ -51,11 +48,11 @@ def dis(x=None):
except TypeError as msg: except TypeError as msg:
print("Sorry:", msg) print("Sorry:", msg)
print() print()
elif hasattr(x, 'co_code'): elif hasattr(x, 'co_code'): # Code object
disassemble(x) disassemble(x)
elif isinstance(x, (bytes, bytearray)): elif isinstance(x, (bytes, bytearray)): # Raw bytecode
_disassemble_bytes(x) _disassemble_bytes(x)
elif isinstance(x, str): elif isinstance(x, str): # Source code
_disassemble_str(x) _disassemble_str(x)
else: else:
raise TypeError("don't know how to disassemble %s objects" % raise TypeError("don't know how to disassemble %s objects" %
@ -97,35 +94,54 @@ def pretty_flags(flags):
names.append(hex(flags)) names.append(hex(flags))
return ", ".join(names) return ", ".join(names)
def code_info(x):
"""Formatted details of methods, functions, or code."""
if hasattr(x, '__func__'): # Method
x = x.__func__
if hasattr(x, '__code__'): # Function
x = x.__code__
if isinstance(x, str): # Source code
x = _try_compile(x, "<code_info>")
if hasattr(x, 'co_code'): # Code object
return _format_code_info(x)
else:
raise TypeError("don't know how to disassemble %s objects" %
type(x).__name__)
def _format_code_info(co):
lines = []
lines.append("Name: %s" % co.co_name)
lines.append("Filename: %s" % co.co_filename)
lines.append("Argument count: %s" % co.co_argcount)
lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
lines.append("Number of locals: %s" % co.co_nlocals)
lines.append("Stack size: %s" % co.co_stacksize)
lines.append("Flags: %s" % pretty_flags(co.co_flags))
if co.co_consts:
lines.append("Constants:")
for i_c in enumerate(co.co_consts):
lines.append("%4d: %r" % i_c)
if co.co_names:
lines.append("Names:")
for i_n in enumerate(co.co_names):
lines.append("%4d: %s" % i_n)
if co.co_varnames:
lines.append("Variable names:")
for i_n in enumerate(co.co_varnames):
lines.append("%4d: %s" % i_n)
if co.co_freevars:
lines.append("Free variables:")
for i_n in enumerate(co.co_freevars):
lines.append("%4d: %s" % i_n)
if co.co_cellvars:
lines.append("Cell variables:")
for i_n in enumerate(co.co_cellvars):
lines.append("%4d: %s" % i_n)
return "\n".join(lines)
def show_code(co): def show_code(co):
"""Show details about a code object.""" """Show details about a code object."""
print("Name: ", co.co_name) print(code_info(co))
print("Filename: ", co.co_filename)
print("Argument count: ", co.co_argcount)
print("Kw-only arguments:", co.co_kwonlyargcount)
print("Number of locals: ", co.co_nlocals)
print("Stack size: ", co.co_stacksize)
print("Flags: ", pretty_flags(co.co_flags))
if co.co_consts:
print("Constants:")
for i_c in enumerate(co.co_consts):
print("%4d: %r" % i_c)
if co.co_names:
print("Names:")
for i_n in enumerate(co.co_names):
print("%4d: %s" % i_n)
if co.co_varnames:
print("Variable names:")
for i_n in enumerate(co.co_varnames):
print("%4d: %s" % i_n)
if co.co_freevars:
print("Free variables:")
for i_n in enumerate(co.co_freevars):
print("%4d: %s" % i_n)
if co.co_cellvars:
print("Cell variables:")
for i_n in enumerate(co.co_cellvars):
print("%4d: %s" % i_n)
def disassemble(co, lasti=-1): def disassemble(co, lasti=-1):
"""Disassemble a code object.""" """Disassemble a code object."""

View file

@ -1,6 +1,6 @@
# Minimal tests for dis module # Minimal tests for dis module
from test.support import run_unittest from test.support import run_unittest, captured_stdout
import unittest import unittest
import sys import sys
import dis import dis
@ -211,8 +211,162 @@ class DisTests(unittest.TestCase):
self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str)
self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str)
code_info_code_info = """\
Name: code_info
Filename: {0}
Argument count: 1
Kw-only arguments: 0
Number of locals: 1
Stack size: 4
Flags: OPTIMIZED, NEWLOCALS, NOFREE
Constants:
0: 'Formatted details of methods, functions, or code.'
1: '__func__'
2: '__code__'
3: '<code_info>'
4: 'co_code'
5: "don't know how to disassemble %s objects"
6: None
Names:
0: hasattr
1: __func__
2: __code__
3: isinstance
4: str
5: _try_compile
6: _format_code_info
7: TypeError
8: type
9: __name__
Variable names:
0: x""".format(dis.__file__)
@staticmethod
def tricky(x, y, z=True, *args, c, d, e=[], **kwds):
def f(c=c):
print(x, y, z, c, d, e, f)
yield x, y, z, c, d, e, f
co_tricky_nested_f = tricky.__func__.__code__.co_consts[1]
code_info_tricky = """\
Name: tricky
Filename: {0}
Argument count: 3
Kw-only arguments: 3
Number of locals: 8
Stack size: 7
Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR
Constants:
0: None
1: <code object f at {1}, file "{0}", line {2}>
Variable names:
0: x
1: y
2: z
3: c
4: d
5: e
6: args
7: kwds
Cell variables:
0: e
1: d
2: f
3: y
4: x
5: z""".format(__file__,
hex(id(co_tricky_nested_f)),
co_tricky_nested_f.co_firstlineno)
code_info_tricky_nested_f = """\
Name: f
Filename: {0}
Argument count: 1
Kw-only arguments: 0
Number of locals: 1
Stack size: 8
Flags: OPTIMIZED, NEWLOCALS, NESTED
Constants:
0: None
Names:
0: print
Variable names:
0: c
Free variables:
0: e
1: d
2: f
3: y
4: x
5: z""".format(__file__)
code_info_expr_str = """\
Name: <module>
Filename: <code_info>
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
Stack size: 2
Flags: NOFREE
Constants:
0: 1
Names:
0: x"""
code_info_simple_stmt_str = """\
Name: <module>
Filename: <code_info>
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
Stack size: 2
Flags: NOFREE
Constants:
0: 1
1: None
Names:
0: x"""
code_info_compound_stmt_str = """\
Name: <module>
Filename: <code_info>
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
Stack size: 2
Flags: NOFREE
Constants:
0: 0
1: 1
2: None
Names:
0: x"""
class CodeInfoTests(unittest.TestCase):
test_pairs = [
(dis.code_info, code_info_code_info),
(tricky, code_info_tricky),
(co_tricky_nested_f, code_info_tricky_nested_f),
(expr_str, code_info_expr_str),
(simple_stmt_str, code_info_simple_stmt_str),
(compound_stmt_str, code_info_compound_stmt_str),
]
def test_code_info(self):
self.maxDiff = 1000
for x, expected in self.test_pairs:
self.assertEqual(dis.code_info(x), expected)
def test_show_code(self):
self.maxDiff = 1000
for x, expected in self.test_pairs:
with captured_stdout() as output:
dis.show_code(x)
self.assertEqual(output.getvalue(), expected+"\n")
def test_main(): def test_main():
run_unittest(DisTests) run_unittest(DisTests, CodeInfoTests)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()

View file

@ -90,6 +90,10 @@ Extensions
Library Library
------- -------
- Issue #9147: Added dis.code_info() which is similar to show_code()
but returns formatted code information in a string rather than
displaying on screen.
- Issue #9567: functools.update_wrapper now adds a __wrapped__ attribute - Issue #9567: functools.update_wrapper now adds a __wrapped__ attribute
pointing to the original callable pointing to the original callable