diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/dis.py | 42 | ||||
-rw-r--r-- | Lib/test/test_dis.py | 32 |
2 files changed, 57 insertions, 17 deletions
@@ -32,20 +32,30 @@ def _try_compile(source, name): return c def dis(x=None, *, file=None, depth=None): - """Disassemble classes, methods, functions, generators, or code. + """Disassemble classes, methods, functions, and other compiled objects. With no argument, disassemble the last traceback. + Compiled objects currently include generator objects, async generator + objects, and coroutine objects, all of which store their code object + in a special attribute. """ if x is None: distb(file=file) return - if hasattr(x, '__func__'): # Method + # Extract functions from methods. + if hasattr(x, '__func__'): x = x.__func__ - if hasattr(x, '__code__'): # Function + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or x = x.__code__ - if hasattr(x, 'gi_code'): # Generator + elif hasattr(x, 'gi_code'): #...a generator object, or x = x.gi_code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. + x = x.cr_code + # Perform the disassembly. if hasattr(x, '__dict__'): # Class or module items = sorted(x.__dict__.items()) for name, x1 in items: @@ -107,16 +117,24 @@ def pretty_flags(flags): return ", ".join(names) def _get_code_object(x): - """Helper to handle methods, functions, generators, strings and raw code objects""" - if hasattr(x, '__func__'): # Method + """Helper to handle methods, compiled or raw code objects, and strings.""" + # Extract functions from methods. + if hasattr(x, '__func__'): x = x.__func__ - if hasattr(x, '__code__'): # Function + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or x = x.__code__ - if hasattr(x, 'gi_code'): # Generator + elif hasattr(x, 'gi_code'): #...a generator object, or x = x.gi_code - if isinstance(x, str): # Source code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. + x = x.cr_code + # Handle source code. + if isinstance(x, str): x = _try_compile(x, "<disassembly>") - if hasattr(x, 'co_code'): # Code object + # By now, if we don't have a code object, we can't disassemble x. + if hasattr(x, 'co_code'): return x raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) @@ -443,8 +461,8 @@ def findlinestarts(code): class Bytecode: """The bytecode operations of a piece of code - Instantiate this with a function, method, string of code, or a code object - (as returned by compile()). + Instantiate this with a function, method, other compiled object, string of + code, or a code object (as returned by compile()). Iterating over this yields the bytecode operations as Instruction instances. """ diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 254b317..bfbbee2 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -331,6 +331,13 @@ dis_fstring = """\ def _g(x): yield x +async def _ag(x): + yield x + +async def _co(x): + async for item in _ag(x): + pass + def _h(y): def foo(x): '''funcdoc''' @@ -390,6 +397,7 @@ Disassembly of <code object <listcomp> at 0x..., file "%s", line %d>: _h.__code__.co_firstlineno + 3, ) + class DisTests(unittest.TestCase): maxDiff = None @@ -531,10 +539,22 @@ class DisTests(unittest.TestCase): self.do_disassembly_test(_C.cm, dis_c_class_method) def test_disassemble_generator(self): - gen_func_disas = self.get_disassembly(_g) # Disassemble generator function - gen_disas = self.get_disassembly(_g(1)) # Disassemble generator itself + gen_func_disas = self.get_disassembly(_g) # Generator function + gen_disas = self.get_disassembly(_g(1)) # Generator iterator self.assertEqual(gen_disas, gen_func_disas) + def test_disassemble_async_generator(self): + agen_func_disas = self.get_disassembly(_ag) # Async generator function + agen_disas = self.get_disassembly(_ag(1)) # Async generator iterator + self.assertEqual(agen_disas, agen_func_disas) + + def test_disassemble_coroutine(self): + coro_func_disas = self.get_disassembly(_co) # Coroutine function + coro = _co(1) # Coroutine object + coro.close() # Avoid a RuntimeWarning (never awaited) + coro_disas = self.get_disassembly(coro) + self.assertEqual(coro_disas, coro_func_disas) + def test_disassemble_fstring(self): self.do_disassembly_test(_fstring, dis_fstring) @@ -1051,11 +1071,13 @@ class BytecodeTests(unittest.TestCase): def test_source_line_in_disassembly(self): # Use the line in the source code - actual = dis.Bytecode(simple).dis()[:3] - expected = "{:>3}".format(simple.__code__.co_firstlineno) + actual = dis.Bytecode(simple).dis() + actual = actual.strip().partition(" ")[0] # extract the line no + expected = str(simple.__code__.co_firstlineno) self.assertEqual(actual, expected) # Use an explicit first line number - actual = dis.Bytecode(simple, first_line=350).dis()[:3] + actual = dis.Bytecode(simple, first_line=350).dis() + actual = actual.strip().partition(" ")[0] # extract the line no self.assertEqual(actual, "350") def test_info(self): |