]> granicus.if.org Git - python/commitdiff
Issue #11816: multiple improvements to the dis module
authorNick Coghlan <ncoghlan@gmail.com>
Mon, 6 May 2013 13:59:20 +0000 (23:59 +1000)
committerNick Coghlan <ncoghlan@gmail.com>
Mon, 6 May 2013 13:59:20 +0000 (23:59 +1000)
* get_instructions generator
* ability to redirect output to a file
* Bytecode and Instruction abstractions

Patch by Nick Coghlan, Ryan Kelly and Thomas Kluyver.

Doc/library/dis.rst
Doc/whatsnew/3.4.rst
Lib/dis.py
Lib/test/test_dis.py
Misc/NEWS

index 613ff42143c422535ec23f49d8c3ebdd45988561..fb669094e832e7abad6f085cf6ae6ff35bb95c10 100644 (file)
@@ -26,7 +26,8 @@ Example: Given the function :func:`myfunc`::
    def myfunc(alist):
        return len(alist)
 
-the following command can be used to get the disassembly of :func:`myfunc`::
+the following command can be used to display the disassembly of
+:func:`myfunc`::
 
    >>> dis.dis(myfunc)
      2           0 LOAD_GLOBAL              0 (len)
@@ -36,8 +37,62 @@ the following command can be used to get the disassembly of :func:`myfunc`::
 
 (The "2" is a line number).
 
-The :mod:`dis` module defines the following functions and constants:
+Bytecode analysis
+-----------------
 
+The bytecode analysis API allows pieces of Python code to be wrapped in a
+:class:`Bytecode` object that provides easy access to details of the
+compiled code.
+
+.. class:: Bytecode
+
+   The bytecode operations of a piece of code
+
+   This is a convenient wrapper around many of the functions listed below.
+   Instantiate it with a function, method, string of code, or a code object
+   (as returned by :func:`compile`).
+
+   Iterating over this yields the bytecode operations as :class:`Instruction`
+   instances.
+
+   .. data:: codeobj
+
+      The compiled code object.
+
+   .. method:: display_code(*, file=None)
+
+      Print a formatted view of the bytecode operations, like :func:`dis`.
+
+   .. method:: info()
+
+      Return a formatted multi-line string with detailed information about the
+      code object, like :func:`code_info`.
+
+   .. method:: show_info(*, file=None)
+
+      Print the information about the code object as returned by :meth:`info`.
+
+   .. versionadded:: 3.4
+
+Example::
+
+    >>> bytecode = dis.Bytecode(myfunc)
+    >>> for instr in bytecode:
+    ...     print(instr.opname)
+    ...
+    LOAD_GLOBAL
+    LOAD_FAST
+    CALL_FUNCTION
+    RETURN_VALUE
+
+
+Analysis functions
+------------------
+
+The :mod:`dis` module also defines the following analysis functions that
+convert the input directly to the desired output. They can be useful if
+only a single operation is being performed, so the intermediate analysis
+object isn't useful:
 
 .. function:: code_info(x)
 
@@ -51,17 +106,21 @@ The :mod:`dis` module defines the following functions and constants:
    .. versionadded:: 3.2
 
 
-.. function:: show_code(x)
+.. function:: show_code(x, *, file=None)
 
    Print detailed code object information for the supplied function, method,
    source code string or code object to stdout.
 
-   This is a convenient shorthand for ``print(code_info(x))``, intended for
-   interactive exploration at the interpreter prompt.
+   This is a convenient shorthand for ``print(code_info(x), file=file)``,
+   intended for interactive exploration at the interpreter prompt.
 
    .. versionadded:: 3.2
 
-.. function:: dis(x=None)
+   .. versionchanged:: 3.4
+      Added ``file`` parameter
+
+
+.. function:: dis(x=None, *, file=None)
 
    Disassemble the *x* object.  *x* can denote either a module, a class, a
    method, a function, a code object, a string of source code or a byte sequence
@@ -72,16 +131,28 @@ The :mod:`dis` module defines the following functions and constants:
    disassembled.  If no object is provided, this function disassembles the last
    traceback.
 
+   The disassembly is written as text to the supplied ``file`` argument if
+   provided and to ``sys.stdout`` otherwise.
+
+   .. versionchanged:: 3.4
+      Added ``file`` parameter
 
-.. function:: distb(tb=None)
+
+.. function:: distb(tb=None, *, file=None)
 
    Disassemble the top-of-stack function of a traceback, using the last
    traceback if none was passed.  The instruction causing the exception is
    indicated.
 
+   The disassembly is written as text to the supplied ``file`` argument if
+   provided and to ``sys.stdout`` otherwise.
+
+   .. versionchanged:: 3.4
+      Added ``file`` parameter
 
-.. function:: disassemble(code, lasti=-1)
-              disco(code, lasti=-1)
+
+.. function:: disassemble(code, lasti=-1, *, file=None)
+              disco(code, lasti=-1, *, file=None)
 
    Disassemble a code object, indicating the last instruction if *lasti* was
    provided.  The output is divided in the following columns:
@@ -97,6 +168,26 @@ The :mod:`dis` module defines the following functions and constants:
    The parameter interpretation recognizes local and global variable names,
    constant values, branch targets, and compare operators.
 
+   The disassembly is written as text to the supplied ``file`` argument if
+   provided and to ``sys.stdout`` otherwise.
+
+   .. versionchanged:: 3.4
+      Added ``file`` parameter
+
+
+.. function:: get_instructions(x, *, line_offset=0)
+
+   Return an iterator over the instructions in the supplied function, method,
+   source code string or code object.
+
+   The iterator generates a series of :class:`Instruction` named tuples
+   giving the details of each operation in the supplied code.
+
+   The given *line_offset* is added to the ``starts_line`` attribute of any
+   instructions that start a new line.
+
+   .. versionadded:: 3.4
+
 
 .. function:: findlinestarts(code)
 
@@ -110,61 +201,60 @@ The :mod:`dis` module defines the following functions and constants:
    Detect all offsets in the code object *code* which are jump targets, and
    return a list of these offsets.
 
+.. _bytecodes:
 
-.. data:: opname
+Python Bytecode Instructions
+----------------------------
 
-   Sequence of operation names, indexable using the bytecode.
+The :func:`get_instructions` function and :class:`Bytecode` class provide
+details of bytecode instructions as :class:`Instruction` instances:
 
+.. class:: Instruction
 
-.. data:: opmap
+   Details for a bytecode operation
 
-   Dictionary mapping operation names to bytecodes.
+   .. data:: opcode
 
+      numeric code for operation, corresponding to the opcode values listed
+      below and the bytecode values in the :ref:`opcode_collections`.
 
-.. data:: cmp_op
 
-   Sequence of all compare operation names.
+   .. data:: opname
 
+      human readable name for operation
 
-.. data:: hasconst
 
-   Sequence of bytecodes that have a constant parameter.
+   .. data:: arg
 
+      numeric argument to operation (if any), otherwise None
 
-.. data:: hasfree
 
-   Sequence of bytecodes that access a free variable.
+   .. data:: argval
 
+      resolved arg value (if known), otherwise same as arg
 
-.. data:: hasname
 
-   Sequence of bytecodes that access an attribute by name.
+   .. data:: argrepr
 
+      human readable description of operation argument
 
-.. data:: hasjrel
 
-   Sequence of bytecodes that have a relative jump target.
+   .. data:: offset
 
+      start index of operation within bytecode sequence
 
-.. data:: hasjabs
 
-   Sequence of bytecodes that have an absolute jump target.
-
-
-.. data:: haslocal
-
-   Sequence of bytecodes that access a local variable.
+   .. data:: starts_line
 
+      line started by this opcode (if any), otherwise None
 
-.. data:: hascompare
 
-   Sequence of bytecodes of Boolean operations.
+   .. data:: is_jump_target
 
+      True if other code jumps to here, otherwise False
 
-.. _bytecodes:
+   .. versionadded:: 3.4
 
-Python Bytecode Instructions
-----------------------------
 
 The Python compiler currently generates the following bytecode instructions.
 
@@ -820,3 +910,62 @@ the more significant byte last.
    which don't take arguments ``< HAVE_ARGUMENT`` and those which do ``>=
    HAVE_ARGUMENT``.
 
+.. _opcode_collections:
+
+Opcode collections
+------------------
+
+These collections are provided for automatic introspection of bytecode
+instructions:
+
+.. data:: opname
+
+   Sequence of operation names, indexable using the bytecode.
+
+
+.. data:: opmap
+
+   Dictionary mapping operation names to bytecodes.
+
+
+.. data:: cmp_op
+
+   Sequence of all compare operation names.
+
+
+.. data:: hasconst
+
+   Sequence of bytecodes that have a constant parameter.
+
+
+.. data:: hasfree
+
+   Sequence of bytecodes that access a free variable (note that 'free' in
+   this context refers to names in the current scope that are referenced by
+   inner scopes or names in outer scopes that are referenced from this scope.
+   It does *not* include references to global or builtin scopes).
+
+
+.. data:: hasname
+
+   Sequence of bytecodes that access an attribute by name.
+
+
+.. data:: hasjrel
+
+   Sequence of bytecodes that have a relative jump target.
+
+
+.. data:: hasjabs
+
+   Sequence of bytecodes that have an absolute jump target.
+
+
+.. data:: haslocal
+
+   Sequence of bytecodes that access a local variable.
+
+
+.. data:: hascompare
+
+   Sequence of bytecodes of Boolean operations.
index 9774241f90cb43beb9189d8225b84479a3845b20..c6b164551702923afea9e7c5c0a716bb673cfcfb 100644 (file)
@@ -152,6 +152,21 @@ Improved Modules
 ================
 
 
+dis
+---
+
+The :mod:`dis` module is now built around an :class:`Instruction` class that
+provides details of individual bytecode operations and a
+:func:`get_instructions` iterator that emits the Instruction stream for a
+given piece of Python code. The various display tools in the :mod:`dis`
+module have been updated to be based on these new components.
+
+The new :class:`dis.Bytecode` class provides an object-oriented API for
+inspecting bytecode, both in human-readable form and for iterating over
+instructions.
+
+(Contributed by Nick Coghlan, Ryan Kelly and Thomas Kluyver in :issue:`11816`)
+
 doctest
 -------
 
index 543fdc7ed069d6e765d95f8cb13a129512de1e63..ca4094c1bb7f3453647b45094db4734e20e602e6 100644 (file)
@@ -2,12 +2,14 @@
 
 import sys
 import types
+import collections
 
 from opcode import *
 from opcode import __all__ as _opcodes_all
 
 __all__ = ["code_info", "dis", "disassemble", "distb", "disco",
-           "findlinestarts", "findlabels", "show_code"] + _opcodes_all
+           "findlinestarts", "findlabels", "show_code",
+           "get_instructions", "Instruction", "Bytecode"] + _opcodes_all
 del _opcodes_all
 
 _have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
@@ -25,7 +27,7 @@ def _try_compile(source, name):
         c = compile(source, name, 'exec')
     return c
 
-def dis(x=None):
+def dis(x=None, *, file=None):
     """Disassemble classes, methods, functions, or code.
 
     With no argument, disassemble the last traceback.
@@ -42,23 +44,23 @@ def dis(x=None):
         items = sorted(x.__dict__.items())
         for name, x1 in items:
             if isinstance(x1, _have_code):
-                print("Disassembly of %s:" % name)
+                print("Disassembly of %s:" % name, file=file)
                 try:
                     dis(x1)
                 except TypeError as msg:
-                    print("Sorry:", msg)
-                print()
+                    print("Sorry:", msg, file=file)
+                print(file=file)
     elif hasattr(x, 'co_code'): # Code object
-        disassemble(x)
+        disassemble(x, file=file)
     elif isinstance(x, (bytes, bytearray)): # Raw bytecode
-        _disassemble_bytes(x)
+        _disassemble_bytes(x, file=file)
     elif isinstance(x, str):    # Source code
-        _disassemble_str(x)
+        _disassemble_str(x, file=file)
     else:
         raise TypeError("don't know how to disassemble %s objects" %
                         type(x).__name__)
 
-def distb(tb=None):
+def distb(tb=None, *, file=None):
     """Disassemble a traceback (default: last traceback)."""
     if tb is None:
         try:
@@ -66,7 +68,7 @@ def distb(tb=None):
         except AttributeError:
             raise RuntimeError("no last traceback to disassemble")
         while tb.tb_next: tb = tb.tb_next
-    disassemble(tb.tb_frame.f_code, tb.tb_lasti)
+    disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file)
 
 # The inspect module interrogates this dictionary to build its
 # list of CO_* constants. It is also used by pretty_flags to
@@ -95,19 +97,22 @@ def pretty_flags(flags):
         names.append(hex(flags))
     return ", ".join(names)
 
-def code_info(x):
-    """Formatted details of methods, functions, or code."""
+def _get_code_object(x):
+    """Helper to handle methods, functions, strings and raw code objects"""
     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>")
+        x = _try_compile(x, "<disassembly>")
     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__)
+        return x
+    raise TypeError("don't know how to disassemble %s objects" %
+                    type(x).__name__)
+
+def code_info(x):
+    """Formatted details of methods, functions, or code."""
+    return _format_code_info(_get_code_object(x))
 
 def _format_code_info(co):
     lines = []
@@ -140,106 +145,196 @@ def _format_code_info(co):
             lines.append("%4d: %s" % i_n)
     return "\n".join(lines)
 
-def show_code(co):
+def show_code(co, *, file=None):
     """Print details of methods, functions, or code to stdout."""
-    print(code_info(co))
+    print(code_info(co), file=file)
 
-def disassemble(co, lasti=-1):
-    """Disassemble a code object."""
-    code = co.co_code
-    labels = findlabels(code)
+_Instruction = collections.namedtuple("_Instruction",
+     "opname opcode arg argval argrepr offset starts_line is_jump_target")
+
+class Instruction(_Instruction):
+    """Details for a bytecode operation
+
+       Defined fields:
+         opname - human readable name for operation
+         opcode - numeric code for operation
+         arg - numeric argument to operation (if any), otherwise None
+         argval - resolved arg value (if known), otherwise same as arg
+         argrepr - human readable description of operation argument
+         offset - start index of operation within bytecode sequence
+         starts_line - line started by this opcode (if any), otherwise None
+         is_jump_target - True if other code jumps to here, otherwise False
+    """
+
+    def _disassemble(self, lineno_width=3, mark_as_current=False):
+        """Format instruction details for inclusion in disassembly output
+
+        *lineno_width* sets the width of the line number field (0 omits it)
+        *mark_as_current* inserts a '-->' marker arrow as part of the line
+        """
+        fields = []
+        # Column: Source code line number
+        if lineno_width:
+            if self.starts_line is not None:
+                lineno_fmt = "%%%dd" % lineno_width
+                fields.append(lineno_fmt % self.starts_line)
+            else:
+                fields.append(' ' * lineno_width)
+        # Column: Current instruction indicator
+        if mark_as_current:
+            fields.append('-->')
+        else:
+            fields.append('   ')
+        # Column: Jump target marker
+        if self.is_jump_target:
+            fields.append('>>')
+        else:
+            fields.append('  ')
+        # Column: Instruction offset from start of code sequence
+        fields.append(repr(self.offset).rjust(4))
+        # Column: Opcode name
+        fields.append(self.opname.ljust(20))
+        # Column: Opcode argument
+        if self.arg is not None:
+            fields.append(repr(self.arg).rjust(5))
+            # Column: Opcode argument details
+            if self.argrepr:
+                fields.append('(' + self.argrepr + ')')
+        return ' '.join(fields)
+
+
+def get_instructions(x, *, line_offset=0):
+    """Iterator for the opcodes in methods, functions or code
+
+    Generates a series of Instruction named tuples giving the details of
+    each operations in the supplied code.
+
+    The given line offset is added to the 'starts_line' attribute of any
+    instructions that start a new line.
+    """
+    co = _get_code_object(x)
+    cell_names = co.co_cellvars + co.co_freevars
     linestarts = dict(findlinestarts(co))
-    n = len(code)
-    i = 0
+    return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
+                                   co.co_consts, cell_names, linestarts,
+                                   line_offset)
+
+def _get_const_info(const_index, const_list):
+    """Helper to get optional details about const references
+
+       Returns the dereferenced constant and its repr if the constant
+       list is defined.
+       Otherwise returns the constant index and its repr().
+    """
+    argval = const_index
+    if const_list is not None:
+        argval = const_list[const_index]
+    return argval, repr(argval)
+
+def _get_name_info(name_index, name_list):
+    """Helper to get optional details about named references
+
+       Returns the dereferenced name as both value and repr if the name
+       list is defined.
+       Otherwise returns the name index and its repr().
+    """
+    argval = name_index
+    if name_list is not None:
+        argval = name_list[name_index]
+        argrepr = argval
+    else:
+        argrepr = repr(argval)
+    return argval, argrepr
+
+
+def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
+                      cells=None, linestarts=None, line_offset=0):
+    """Iterate over the instructions in a bytecode string.
+
+    Generates a sequence of Instruction namedtuples giving the details of each
+    opcode.  Additional information about the code's runtime environment
+    (e.g. variable names, constants) can be specified using optional
+    arguments.
+
+    """
+    labels = findlabels(code)
     extended_arg = 0
+    starts_line = None
     free = None
+    # enumerate() is not an option, since we sometimes process
+    # multiple elements on a single pass through the loop
+    n = len(code)
+    i = 0
     while i < n:
         op = code[i]
-        if i in linestarts:
-            if i > 0:
-                print()
-            print("%3d" % linestarts[i], end=' ')
-        else:
-            print('   ', end=' ')
-
-        if i == lasti: print('-->', end=' ')
-        else: print('   ', end=' ')
-        if i in labels: print('>>', end=' ')
-        else: print('  ', end=' ')
-        print(repr(i).rjust(4), end=' ')
-        print(opname[op].ljust(20), end=' ')
+        offset = i
+        if linestarts is not None:
+            starts_line = linestarts.get(i, None)
+            if starts_line is not None:
+                starts_line += line_offset
+        is_jump_target = i in labels
         i = i+1
+        arg = None
+        argval = None
+        argrepr = ''
         if op >= HAVE_ARGUMENT:
-            oparg = code[i] + code[i+1]*256 + extended_arg
+            arg = code[i] + code[i+1]*256 + extended_arg
             extended_arg = 0
             i = i+2
             if op == EXTENDED_ARG:
-                extended_arg = oparg*65536
-            print(repr(oparg).rjust(5), end=' ')
+                extended_arg = arg*65536
+            #  Set argval to the dereferenced value of the argument when
+            #  availabe, and argrepr to the string representation of argval.
+            #    _disassemble_bytes needs the string repr of the
+            #    raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
+            argval = arg
             if op in hasconst:
-                print('(' + repr(co.co_consts[oparg]) + ')', end=' ')
+                argval, argrepr = _get_const_info(arg, constants)
             elif op in hasname:
-                print('(' + co.co_names[oparg] + ')', end=' ')
+                argval, argrepr = _get_name_info(arg, names)
             elif op in hasjrel:
-                print('(to ' + repr(i + oparg) + ')', end=' ')
+                argval = i + arg
+                argrepr = "to " + repr(argval)
             elif op in haslocal:
-                print('(' + co.co_varnames[oparg] + ')', end=' ')
+                argval, argrepr = _get_name_info(arg, varnames)
             elif op in hascompare:
-                print('(' + cmp_op[oparg] + ')', end=' ')
+                argval = cmp_op[arg]
+                argrepr = argval
             elif op in hasfree:
-                if free is None:
-                    free = co.co_cellvars + co.co_freevars
-                print('(' + free[oparg] + ')', end=' ')
+                argval, argrepr = _get_name_info(arg, cells)
             elif op in hasnargs:
-                print('(%d positional, %d keyword pair)'
-                      % (code[i-2], code[i-1]), end=' ')
-        print()
+                argrepr = "%d positional, %d keyword pair" % (code[i-2], code[i-1])
+        yield Instruction(opname[op], op,
+                          arg, argval, argrepr,
+                          offset, starts_line, is_jump_target)
+
+def disassemble(co, lasti=-1, *, file=None):
+    """Disassemble a code object."""
+    cell_names = co.co_cellvars + co.co_freevars
+    linestarts = dict(findlinestarts(co))
+    _disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names,
+                       co.co_consts, cell_names, linestarts, file=file)
 
 def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
-                       constants=None):
-    labels = findlabels(code)
-    n = len(code)
-    i = 0
-    while i < n:
-        op = code[i]
-        if i == lasti: print('-->', end=' ')
-        else: print('   ', end=' ')
-        if i in labels: print('>>', end=' ')
-        else: print('  ', end=' ')
-        print(repr(i).rjust(4), end=' ')
-        print(opname[op].ljust(15), end=' ')
-        i = i+1
-        if op >= HAVE_ARGUMENT:
-            oparg = code[i] + code[i+1]*256
-            i = i+2
-            print(repr(oparg).rjust(5), end=' ')
-            if op in hasconst:
-                if constants:
-                    print('(' + repr(constants[oparg]) + ')', end=' ')
-                else:
-                    print('(%d)'%oparg, end=' ')
-            elif op in hasname:
-                if names is not None:
-                    print('(' + names[oparg] + ')', end=' ')
-                else:
-                    print('(%d)'%oparg, end=' ')
-            elif op in hasjrel:
-                print('(to ' + repr(i + oparg) + ')', end=' ')
-            elif op in haslocal:
-                if varnames:
-                    print('(' + varnames[oparg] + ')', end=' ')
-                else:
-                    print('(%d)' % oparg, end=' ')
-            elif op in hascompare:
-                print('(' + cmp_op[oparg] + ')', end=' ')
-            elif op in hasnargs:
-                print('(%d positional, %d keyword pair)'
-                      % (code[i-2], code[i-1]), end=' ')
-        print()
+                       constants=None, cells=None, linestarts=None,
+                       *, file=None):
+    # Omit the line number column entirely if we have no line number info
+    show_lineno = linestarts is not None
+    # TODO?: Adjust width upwards if max(linestarts.values()) >= 1000?
+    lineno_width = 3 if show_lineno else 0
+    for instr in _get_instructions_bytes(code, varnames, names,
+                                         constants, cells, linestarts):
+        new_source_line = (show_lineno and
+                           instr.starts_line is not None and
+                           instr.offset > 0)
+        if new_source_line:
+            print(file=file)
+        is_current_instr = instr.offset == lasti
+        print(instr._disassemble(lineno_width, is_current_instr), file=file)
 
-def _disassemble_str(source):
+def _disassemble_str(source, *, file=None):
     """Compile the source string, then disassemble the code object."""
-    disassemble(_try_compile(source, '<dis>'))
+    disassemble(_try_compile(source, '<dis>'), file=file)
 
 disco = disassemble                     # XXX For backwards compatibility
 
@@ -250,19 +345,21 @@ def findlabels(code):
 
     """
     labels = []
+    # enumerate() is not an option, since we sometimes process
+    # multiple elements on a single pass through the loop
     n = len(code)
     i = 0
     while i < n:
         op = code[i]
         i = i+1
         if op >= HAVE_ARGUMENT:
-            oparg = code[i] + code[i+1]*256
+            arg = code[i] + code[i+1]*256
             i = i+2
             label = -1
             if op in hasjrel:
-                label = i+oparg
+                label = i+arg
             elif op in hasjabs:
-                label = oparg
+                label = arg
             if label >= 0:
                 if label not in labels:
                     labels.append(label)
@@ -290,6 +387,50 @@ def findlinestarts(code):
     if lineno != lastlineno:
         yield (addr, lineno)
 
+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()).
+
+    Iterating over this yields the bytecode operations as Instruction instances.
+    """
+    def __init__(self, x):
+        self.codeobj = _get_code_object(x)
+        self.cell_names = self.codeobj.co_cellvars + self.codeobj.co_freevars
+        self.linestarts = dict(findlinestarts(self.codeobj))
+        self.line_offset = 0
+        self.original_object = x
+
+    def __iter__(self):
+        co = self.codeobj
+        return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
+                                   co.co_consts, self.cell_names,
+                                   self.linestarts, self.line_offset)
+
+    def __repr__(self):
+        return "{}({!r})".format(self.__class__.__name__, self.original_object)
+
+    def info(self):
+        """Return formatted information about the code object."""
+        return _format_code_info(self.codeobj)
+
+    def show_info(self, *, file=None):
+        """Print the information about the code object as returned by info()."""
+        print(self.info(), file=file)
+
+    def display_code(self, *, file=None):
+        """Print a formatted view of the bytecode operations.
+        """
+        co = self.codeobj
+        return _disassemble_bytes(co.co_code, varnames=co.co_varnames,
+                                  names=co.co_names, constants=co.co_consts,
+                                  cells=self.cell_names,
+                                  linestarts=self.linestarts,
+                                  file=file
+                                 )
+
+
 def _test():
     """Simple test program to disassemble a file."""
     if sys.argv[1:]:
index 1bcd693f63a3e4aca8f228f4e14daae3627e2d73..a1c658269791d6a5eb15f8b0c2c03f2528ad0b3e 100644 (file)
@@ -1,11 +1,13 @@
 # Minimal tests for dis module
 
 from test.support import run_unittest, captured_stdout
+from test.bytecode_helper import BytecodeTestCase
 import difflib
 import unittest
 import sys
 import dis
 import io
+import types
 
 class _C:
     def __init__(self, x):
@@ -22,12 +24,12 @@ dis_c_instance_method = """\
 """ % (_C.__init__.__code__.co_firstlineno + 1,)
 
 dis_c_instance_method_bytes = """\
-          0 LOAD_FAST           1 (1)
-          3 LOAD_CONST          1 (1)
-          6 COMPARE_OP          2 (==)
-          9 LOAD_FAST           0 (0)
-         12 STORE_ATTR          0 (0)
-         15 LOAD_CONST          0 (0)
+          0 LOAD_FAST                1 (1)
+          3 LOAD_CONST               1 (1)
+          6 COMPARE_OP               2 (==)
+          9 LOAD_FAST                0 (0)
+         12 STORE_ATTR               0 (0)
+         15 LOAD_CONST               0 (0)
          18 RETURN_VALUE
 """
 
@@ -48,11 +50,11 @@ dis_f = """\
 
 
 dis_f_co_code = """\
-          0 LOAD_GLOBAL         0 (0)
-          3 LOAD_FAST           0 (0)
-          6 CALL_FUNCTION       1 (1 positional, 0 keyword pair)
+          0 LOAD_GLOBAL              0 (0)
+          3 LOAD_FAST                0 (0)
+          6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
           9 POP_TOP
-         10 LOAD_CONST          1 (1)
+         10 LOAD_CONST               1 (1)
          13 RETURN_VALUE
 """
 
@@ -298,29 +300,16 @@ Filename:          (.*)
 Argument count:    1
 Kw-only arguments: 0
 Number of locals:  1
-Stack size:        4
+Stack size:        3
 Flags:             OPTIMIZED, NEWLOCALS, NOFREE
 Constants:
    0: %r
-   1: '__func__'
-   2: '__code__'
-   3: '<code_info>'
-   4: 'co_code'
-   5: "don't know how to disassemble %%s objects"
-%sNames:
-   0: hasattr
-   1: __func__
-   2: __code__
-   3: isinstance
-   4: str
-   5: _try_compile
-   6: _format_code_info
-   7: TypeError
-   8: type
-   9: __name__
+Names:
+   0: _format_code_info
+   1: _get_code_object
 Variable names:
-   0: x""" % (('Formatted details of methods, functions, or code.', '   6: None\n')
-              if sys.flags.optimize < 2 else (None, ''))
+   0: x""" % (('Formatted details of methods, functions, or code.',)
+              if sys.flags.optimize < 2 else (None,))
 
 @staticmethod
 def tricky(x, y, z=True, *args, c, d, e=[], **kwds):
@@ -384,7 +373,7 @@ Free variables:
 
 code_info_expr_str = """\
 Name:              <module>
-Filename:          <code_info>
+Filename:          <disassembly>
 Argument count:    0
 Kw-only arguments: 0
 Number of locals:  0
@@ -397,7 +386,7 @@ Names:
 
 code_info_simple_stmt_str = """\
 Name:              <module>
-Filename:          <code_info>
+Filename:          <disassembly>
 Argument count:    0
 Kw-only arguments: 0
 Number of locals:  0
@@ -411,7 +400,7 @@ Names:
 
 code_info_compound_stmt_str = """\
 Name:              <module>
-Filename:          <code_info>
+Filename:          <disassembly>
 Argument count:    0
 Kw-only arguments: 0
 Number of locals:  0
@@ -445,6 +434,9 @@ class CodeInfoTests(unittest.TestCase):
             with captured_stdout() as output:
                 dis.show_code(x)
             self.assertRegex(output.getvalue(), expected+"\n")
+            output = io.StringIO()
+            dis.show_code(x, file=output)
+            self.assertRegex(output.getvalue(), expected)
 
     def test_code_info_object(self):
         self.assertRaises(TypeError, dis.code_info, object())
@@ -453,8 +445,289 @@ class CodeInfoTests(unittest.TestCase):
         self.assertEqual(dis.pretty_flags(0), '0x0')
 
 
+# Fodder for instruction introspection tests
+#   Editing any of these may require recalculating the expected output
+def outer(a=1, b=2):
+    def f(c=3, d=4):
+        def inner(e=5, f=6):
+            print(a, b, c, d, e, f)
+        print(a, b, c, d)
+        return inner
+    print(a, b, '', 1, [], {}, "Hello world!")
+    return f
+
+def jumpy():
+    # This won't actually run (but that's OK, we only disassemble it)
+    for i in range(10):
+        print(i)
+        if i < 4:
+            continue
+        if i > 6:
+            break
+    else:
+        print("I can haz else clause?")
+    while i:
+        print(i)
+        i -= 1
+        if i > 6:
+            continue
+        if i < 4:
+            break
+    else:
+        print("Who let lolcatz into this test suite?")
+    try:
+        1 / 0
+    except ZeroDivisionError:
+        print("Here we go, here we go, here we go...")
+    else:
+        with i as dodgy:
+            print("Never reach this")
+    finally:
+        print("OK, now we're done")
+
+# End fodder for opinfo generation tests
+expected_outer_offset = 1 - outer.__code__.co_firstlineno
+expected_jumpy_offset = 1 - jumpy.__code__.co_firstlineno
+code_object_f = outer.__code__.co_consts[3]
+code_object_inner = code_object_f.co_consts[3]
+
+# The following lines are useful to regenerate the expected results after
+# either the fodder is modified or the bytecode generation changes
+# After regeneration, update the references to code_object_f and
+# code_object_inner before rerunning the tests
+
+#_instructions = dis.get_instructions(outer, line_offset=expected_outer_offset)
+#print('expected_opinfo_outer = [\n  ',
+      #',\n  '.join(map(str, _instructions)), ',\n]', sep='')
+#_instructions = dis.get_instructions(outer(), line_offset=expected_outer_offset)
+#print('expected_opinfo_f = [\n  ',
+      #',\n  '.join(map(str, _instructions)), ',\n]', sep='')
+#_instructions = dis.get_instructions(outer()(), line_offset=expected_outer_offset)
+#print('expected_opinfo_inner = [\n  ',
+      #',\n  '.join(map(str, _instructions)), ',\n]', sep='')
+#_instructions = dis.get_instructions(jumpy, line_offset=expected_jumpy_offset)
+#print('expected_opinfo_jumpy = [\n  ',
+      #',\n  '.join(map(str, _instructions)), ',\n]', sep='')
+
+
+Instruction = dis.Instruction
+expected_opinfo_outer = [
+  Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=3, argrepr='3', offset=0, starts_line=2, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=3, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='a', argrepr='a', offset=6, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='b', argrepr='b', offset=9, starts_line=None, is_jump_target=False),
+  Instruction(opname='BUILD_TUPLE', opcode=102, arg=2, argval=2, argrepr='', offset=12, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_f, argrepr=repr(code_object_f), offset=15, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f', argrepr="'outer.<locals>.f'", offset=18, starts_line=None, is_jump_target=False),
+  Instruction(opname='MAKE_CLOSURE', opcode=134, arg=2, argval=2, argrepr='', offset=21, starts_line=None, is_jump_target=False),
+  Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='f', argrepr='f', offset=24, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=27, starts_line=7, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=30, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=33, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval='', argrepr="''", offset=36, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval=1, argrepr='1', offset=39, starts_line=None, is_jump_target=False),
+  Instruction(opname='BUILD_LIST', opcode=103, arg=0, argval=0, argrepr='', offset=42, starts_line=None, is_jump_target=False),
+  Instruction(opname='BUILD_MAP', opcode=105, arg=0, argval=0, argrepr='', offset=45, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval='Hello world!', argrepr="'Hello world!'", offset=48, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=7, argval=7, argrepr='7 positional, 0 keyword pair', offset=51, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=54, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='f', argrepr='f', offset=55, starts_line=8, is_jump_target=False),
+  Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=58, starts_line=None, is_jump_target=False),
+]
+
+expected_opinfo_f = [
+  Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=5, argrepr='5', offset=0, starts_line=3, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=6, argrepr='6', offset=3, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CLOSURE', opcode=135, arg=2, argval='a', argrepr='a', offset=6, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CLOSURE', opcode=135, arg=3, argval='b', argrepr='b', offset=9, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='c', argrepr='c', offset=12, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='d', argrepr='d', offset=15, starts_line=None, is_jump_target=False),
+  Instruction(opname='BUILD_TUPLE', opcode=102, arg=4, argval=4, argrepr='', offset=18, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_inner, argrepr=repr(code_object_inner), offset=21, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f.<locals>.inner', argrepr="'outer.<locals>.f.<locals>.inner'", offset=24, starts_line=None, is_jump_target=False),
+  Instruction(opname='MAKE_CLOSURE', opcode=134, arg=2, argval=2, argrepr='', offset=27, starts_line=None, is_jump_target=False),
+  Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='inner', argrepr='inner', offset=30, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=33, starts_line=5, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=2, argval='a', argrepr='a', offset=36, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=3, argval='b', argrepr='b', offset=39, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='c', argrepr='c', offset=42, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='d', argrepr='d', offset=45, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=4, argval=4, argrepr='4 positional, 0 keyword pair', offset=48, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=51, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='inner', argrepr='inner', offset=52, starts_line=6, is_jump_target=False),
+  Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=55, starts_line=None, is_jump_target=False),
+]
+
+expected_opinfo_inner = [
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=0, starts_line=4, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=3, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=6, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=2, argval='c', argrepr='c', offset=9, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_DEREF', opcode=136, arg=3, argval='d', argrepr='d', offset=12, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='e', argrepr='e', offset=15, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='f', argrepr='f', offset=18, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=6, argval=6, argrepr='6 positional, 0 keyword pair', offset=21, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=24, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=25, starts_line=None, is_jump_target=False),
+  Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=28, starts_line=None, is_jump_target=False),
+]
+
+expected_opinfo_jumpy = [
+  Instruction(opname='SETUP_LOOP', opcode=120, arg=74, argval=77, argrepr='to 77', offset=0, starts_line=3, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='range', argrepr='range', offset=3, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=10, argrepr='10', offset=6, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=9, starts_line=None, is_jump_target=False),
+  Instruction(opname='GET_ITER', opcode=68, arg=None, argval=None, argrepr='', offset=12, starts_line=None, is_jump_target=False),
+  Instruction(opname='FOR_ITER', opcode=93, arg=50, argval=66, argrepr='to 66', offset=13, starts_line=None, is_jump_target=True),
+  Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=16, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=19, starts_line=4, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=22, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=25, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=28, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=29, starts_line=5, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=32, starts_line=None, is_jump_target=False),
+  Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=35, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=47, argval=47, argrepr='', offset=38, starts_line=None, is_jump_target=False),
+  Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=41, starts_line=6, is_jump_target=False),
+  Instruction(opname='JUMP_FORWARD', opcode=110, arg=0, argval=47, argrepr='to 47', offset=44, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=47, starts_line=7, is_jump_target=True),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=50, starts_line=None, is_jump_target=False),
+  Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=53, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=13, argval=13, argrepr='', offset=56, starts_line=None, is_jump_target=False),
+  Instruction(opname='BREAK_LOOP', opcode=80, arg=None, argval=None, argrepr='', offset=59, starts_line=8, is_jump_target=False),
+  Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=60, starts_line=None, is_jump_target=False),
+  Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=63, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=66, starts_line=None, is_jump_target=True),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=67, starts_line=10, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=70, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=73, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=76, starts_line=None, is_jump_target=False),
+  Instruction(opname='SETUP_LOOP', opcode=120, arg=74, argval=154, argrepr='to 154', offset=77, starts_line=11, is_jump_target=True),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=80, starts_line=None, is_jump_target=True),
+  Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=143, argval=143, argrepr='', offset=83, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=86, starts_line=12, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=89, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=92, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=95, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=96, starts_line=13, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=99, starts_line=None, is_jump_target=False),
+  Instruction(opname='INPLACE_SUBTRACT', opcode=56, arg=None, argval=None, argrepr='', offset=102, starts_line=None, is_jump_target=False),
+  Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=103, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=106, starts_line=14, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=109, starts_line=None, is_jump_target=False),
+  Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=112, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=124, argval=124, argrepr='', offset=115, starts_line=None, is_jump_target=False),
+  Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=118, starts_line=15, is_jump_target=False),
+  Instruction(opname='JUMP_FORWARD', opcode=110, arg=0, argval=124, argrepr='to 124', offset=121, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=124, starts_line=16, is_jump_target=True),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=127, starts_line=None, is_jump_target=False),
+  Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=130, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=80, argval=80, argrepr='', offset=133, starts_line=None, is_jump_target=False),
+  Instruction(opname='BREAK_LOOP', opcode=80, arg=None, argval=None, argrepr='', offset=136, starts_line=17, is_jump_target=False),
+  Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=137, starts_line=None, is_jump_target=False),
+  Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=140, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=143, starts_line=None, is_jump_target=True),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=144, starts_line=19, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=147, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=150, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=153, starts_line=None, is_jump_target=False),
+  Instruction(opname='SETUP_FINALLY', opcode=122, arg=72, argval=229, argrepr='to 229', offset=154, starts_line=20, is_jump_target=True),
+  Instruction(opname='SETUP_EXCEPT', opcode=121, arg=12, argval=172, argrepr='to 172', offset=157, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=160, starts_line=21, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=163, starts_line=None, is_jump_target=False),
+  Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=167, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=168, starts_line=None, is_jump_target=False),
+  Instruction(opname='JUMP_FORWARD', opcode=110, arg=28, argval=200, argrepr='to 200', offset=169, starts_line=None, is_jump_target=False),
+  Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=172, starts_line=22, is_jump_target=True),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=2, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=173, starts_line=None, is_jump_target=False),
+  Instruction(opname='COMPARE_OP', opcode=107, arg=10, argval='exception match', argrepr='exception match', offset=176, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=199, argval=199, argrepr='', offset=179, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=183, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=184, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=185, starts_line=23, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=188, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=191, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=194, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=195, starts_line=None, is_jump_target=False),
+  Instruction(opname='JUMP_FORWARD', opcode=110, arg=26, argval=225, argrepr='to 225', offset=196, starts_line=None, is_jump_target=False),
+  Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=199, starts_line=None, is_jump_target=True),
+  Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=200, starts_line=25, is_jump_target=True),
+  Instruction(opname='SETUP_WITH', opcode=143, arg=17, argval=223, argrepr='to 223', offset=203, starts_line=None, is_jump_target=False),
+  Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=206, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=209, starts_line=26, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Never reach this', argrepr="'Never reach this'", offset=212, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=215, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=218, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=219, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=220, starts_line=None, is_jump_target=False),
+  Instruction(opname='WITH_CLEANUP', opcode=81, arg=None, argval=None, argrepr='', offset=223, starts_line=None, is_jump_target=True),
+  Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=224, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=225, starts_line=None, is_jump_target=True),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=226, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=229, starts_line=28, is_jump_target=True),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=232, starts_line=None, is_jump_target=False),
+  Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=235, starts_line=None, is_jump_target=False),
+  Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=238, starts_line=None, is_jump_target=False),
+  Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=239, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=240, starts_line=None, is_jump_target=False),
+  Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=243, starts_line=None, is_jump_target=False),
+]
+
+class InstructionTests(BytecodeTestCase):
+    def test_outer(self):
+        self.assertBytecodeExactlyMatches(outer, expected_opinfo_outer,
+                                          line_offset=expected_outer_offset)
+
+    def test_nested(self):
+        with captured_stdout():
+            f = outer()
+        self.assertBytecodeExactlyMatches(f, expected_opinfo_f,
+                                          line_offset=expected_outer_offset)
+
+    def test_doubly_nested(self):
+        with captured_stdout():
+            inner = outer()()
+        self.assertBytecodeExactlyMatches(inner, expected_opinfo_inner,
+                                          line_offset=expected_outer_offset)
+
+    def test_jumpy(self):
+        self.assertBytecodeExactlyMatches(jumpy, expected_opinfo_jumpy,
+                                          line_offset=expected_jumpy_offset)
+
+class BytecodeTests(unittest.TestCase):
+    def test_instantiation(self):
+        # Test with function, method, code string and code object
+        for obj in [_f, _C(1).__init__, "a=1", _f.__code__]:
+            b = dis.Bytecode(obj)
+            self.assertIsInstance(b.codeobj, types.CodeType)
+
+        self.assertRaises(TypeError, dis.Bytecode, object())
+
+    def test_iteration(self):
+        b = dis.Bytecode(_f)
+        for instr in b:
+            self.assertIsInstance(instr, dis.Instruction)
+
+        assert len(list(b)) > 0  # Iterating should yield at least 1 instruction
+
+    def test_info(self):
+        self.maxDiff = 1000
+        for x, expected in CodeInfoTests.test_pairs:
+            b = dis.Bytecode(x)
+            self.assertRegex(b.info(), expected)
+
+    def test_display_code(self):
+        b = dis.Bytecode(_f)
+        output = io.StringIO()
+        b.display_code(file=output)
+        result = [line.rstrip() for line in output.getvalue().splitlines()]
+        self.assertEqual(result, dis_f.splitlines())
+
+
 def test_main():
-    run_unittest(DisTests, CodeInfoTests)
+    run_unittest(DisTests, CodeInfoTests, InstructionTests, BytecodeTests)
 
 if __name__ == "__main__":
     test_main()
index 855e744c61d75c76cac54a235881ccaf47b5ffee..e7062dd2e1269e95261cb6e8678d5f2f5f61af46 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -74,6 +74,10 @@ Core and Builtins
 Library
 -------
 
+- Issue #11816: multiple improvements to the dis module: get_instructions
+  generator, ability to redirect output to a file, Bytecode and Instruction
+  abstractions. Patch by Nick Coghlan, Ryan Kelly and Thomas Kluyver.
+
 - Issue #13831: Embed stringification of remote traceback in local
   traceback raised when pool task raises an exception.