Issue 9147: Add dis.code_info()
This commit is contained in:
parent
9887683f74
commit
eae2da1da7
|
@ -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:
|
||||
|
||||
|
||||
.. 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)
|
||||
|
||||
Disassemble the *x* object. *x* can denote either a module, a
|
||||
|
|
88
Lib/dis.py
88
Lib/dis.py
|
@ -19,9 +19,6 @@ def _try_compile(source, name):
|
|||
Utility function to accept strings in functions that otherwise
|
||||
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:
|
||||
c = compile(source, name, 'eval')
|
||||
except SyntaxError:
|
||||
|
@ -37,11 +34,11 @@ def dis(x=None):
|
|||
if x is None:
|
||||
distb()
|
||||
return
|
||||
if hasattr(x, '__func__'):
|
||||
if hasattr(x, '__func__'): # Method
|
||||
x = x.__func__
|
||||
if hasattr(x, '__code__'):
|
||||
if hasattr(x, '__code__'): # Function
|
||||
x = x.__code__
|
||||
if hasattr(x, '__dict__'):
|
||||
if hasattr(x, '__dict__'): # Class or module
|
||||
items = sorted(x.__dict__.items())
|
||||
for name, x1 in items:
|
||||
if isinstance(x1, _have_code):
|
||||
|
@ -51,11 +48,11 @@ def dis(x=None):
|
|||
except TypeError as msg:
|
||||
print("Sorry:", msg)
|
||||
print()
|
||||
elif hasattr(x, 'co_code'):
|
||||
elif hasattr(x, 'co_code'): # Code object
|
||||
disassemble(x)
|
||||
elif isinstance(x, (bytes, bytearray)):
|
||||
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
|
||||
_disassemble_bytes(x)
|
||||
elif isinstance(x, str):
|
||||
elif isinstance(x, str): # Source code
|
||||
_disassemble_str(x)
|
||||
else:
|
||||
raise TypeError("don't know how to disassemble %s objects" %
|
||||
|
@ -97,35 +94,54 @@ def pretty_flags(flags):
|
|||
names.append(hex(flags))
|
||||
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):
|
||||
"""Show details about a code object."""
|
||||
print("Name: ", co.co_name)
|
||||
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)
|
||||
print(code_info(co))
|
||||
|
||||
def disassemble(co, lasti=-1):
|
||||
"""Disassemble a code object."""
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Minimal tests for dis module
|
||||
|
||||
from test.support import run_unittest
|
||||
from test.support import run_unittest, captured_stdout
|
||||
import unittest
|
||||
import sys
|
||||
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(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():
|
||||
run_unittest(DisTests)
|
||||
run_unittest(DisTests, CodeInfoTests)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
||||
|
|
|
@ -90,6 +90,10 @@ Extensions
|
|||
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
|
||||
pointing to the original callable
|
||||
|
||||
|
|
Loading…
Reference in New Issue