summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/dis.py42
-rw-r--r--Lib/test/test_dis.py32
2 files changed, 57 insertions, 17 deletions
diff --git a/Lib/dis.py b/Lib/dis.py
index b990839..90ddf4f 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -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):