From eae2da1da78bd13331c4a64ae02fb825eba6777e Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 17 Aug 2010 08:03:36 +0000 Subject: Issue 9147: Add dis.code_info() --- Doc/library/dis.rst | 12 ++++ Lib/dis.py | 72 ++++++++++++++--------- Lib/test/test_dis.py | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++- Misc/NEWS | 4 ++ 4 files changed, 216 insertions(+), 30 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 1d5b223..aa2c552 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -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 diff --git a/Lib/dis.py b/Lib/dis.py index 2f46774..b4159e0 100644 --- a/Lib/dis.py +++ b/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 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)) +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, "") + 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: - print("Constants:") + lines.append("Constants:") for i_c in enumerate(co.co_consts): - print("%4d: %r" % i_c) + lines.append("%4d: %r" % i_c) if co.co_names: - print("Names:") + lines.append("Names:") for i_n in enumerate(co.co_names): - print("%4d: %s" % i_n) + lines.append("%4d: %s" % i_n) if co.co_varnames: - print("Variable names:") + lines.append("Variable names:") for i_n in enumerate(co.co_varnames): - print("%4d: %s" % i_n) + lines.append("%4d: %s" % i_n) if co.co_freevars: - print("Free variables:") + lines.append("Free variables:") for i_n in enumerate(co.co_freevars): - print("%4d: %s" % i_n) + lines.append("%4d: %s" % i_n) if co.co_cellvars: - print("Cell variables:") + lines.append("Cell variables:") for i_n in enumerate(co.co_cellvars): - print("%4d: %s" % i_n) + lines.append("%4d: %s" % i_n) + return "\n".join(lines) + +def show_code(co): + """Show details about a code object.""" + print(code_info(co)) def disassemble(co, lasti=-1): """Disassemble a code object.""" diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 7feee64..8f1783d 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -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: '' + 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: +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: +Filename: +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: +Filename: +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: +Filename: +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() diff --git a/Misc/NEWS b/Misc/NEWS index 7ec59b3..cff5864 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -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 -- cgit v0.12