mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			535 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			535 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#  Author:      Fred L. Drake, Jr.
 | 
						|
#               fdrake@acm.org
 | 
						|
#
 | 
						|
#  This is a simple little module I wrote to make life easier.  I didn't
 | 
						|
#  see anything quite like it in the library, though I may have overlooked
 | 
						|
#  something.  I wrote this when I was trying to read some heavily nested
 | 
						|
#  tuples with fairly non-descriptive content.  This is modeled very much
 | 
						|
#  after Lisp/Scheme - style pretty-printing of lists.  If you find it
 | 
						|
#  useful, thank small children who sleep at night.
 | 
						|
 | 
						|
"""Support to pretty-print lists, tuples, & dictionaries recursively.
 | 
						|
 | 
						|
Very simple, but useful, especially in debugging data structures.
 | 
						|
 | 
						|
Classes
 | 
						|
-------
 | 
						|
 | 
						|
PrettyPrinter()
 | 
						|
    Handle pretty-printing operations onto a stream using a configured
 | 
						|
    set of formatting parameters.
 | 
						|
 | 
						|
Functions
 | 
						|
---------
 | 
						|
 | 
						|
pformat()
 | 
						|
    Format a Python object into a pretty-printed representation.
 | 
						|
 | 
						|
pprint()
 | 
						|
    Pretty-print a Python object to a stream [default is sys.stdout].
 | 
						|
 | 
						|
saferepr()
 | 
						|
    Generate a 'standard' repr()-like value, but protect against recursive
 | 
						|
    data structures.
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
import collections as _collections
 | 
						|
import re
 | 
						|
import sys as _sys
 | 
						|
import types as _types
 | 
						|
from io import StringIO as _StringIO
 | 
						|
 | 
						|
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
 | 
						|
           "PrettyPrinter"]
 | 
						|
 | 
						|
 | 
						|
def pprint(object, stream=None, indent=1, width=80, depth=None, *,
 | 
						|
           compact=False):
 | 
						|
    """Pretty-print a Python object to a stream [default is sys.stdout]."""
 | 
						|
    printer = PrettyPrinter(
 | 
						|
        stream=stream, indent=indent, width=width, depth=depth,
 | 
						|
        compact=compact)
 | 
						|
    printer.pprint(object)
 | 
						|
 | 
						|
def pformat(object, indent=1, width=80, depth=None, *, compact=False):
 | 
						|
    """Format a Python object into a pretty-printed representation."""
 | 
						|
    return PrettyPrinter(indent=indent, width=width, depth=depth,
 | 
						|
                         compact=compact).pformat(object)
 | 
						|
 | 
						|
def saferepr(object):
 | 
						|
    """Version of repr() which can handle recursive data structures."""
 | 
						|
    return _safe_repr(object, {}, None, 0)[0]
 | 
						|
 | 
						|
def isreadable(object):
 | 
						|
    """Determine if saferepr(object) is readable by eval()."""
 | 
						|
    return _safe_repr(object, {}, None, 0)[1]
 | 
						|
 | 
						|
def isrecursive(object):
 | 
						|
    """Determine if object requires a recursive representation."""
 | 
						|
    return _safe_repr(object, {}, None, 0)[2]
 | 
						|
 | 
						|
class _safe_key:
 | 
						|
    """Helper function for key functions when sorting unorderable objects.
 | 
						|
 | 
						|
    The wrapped-object will fallback to an Py2.x style comparison for
 | 
						|
    unorderable types (sorting first comparing the type name and then by
 | 
						|
    the obj ids).  Does not work recursively, so dict.items() must have
 | 
						|
    _safe_key applied to both the key and the value.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    __slots__ = ['obj']
 | 
						|
 | 
						|
    def __init__(self, obj):
 | 
						|
        self.obj = obj
 | 
						|
 | 
						|
    def __lt__(self, other):
 | 
						|
        try:
 | 
						|
            rv = self.obj.__lt__(other.obj)
 | 
						|
        except TypeError:
 | 
						|
            rv = NotImplemented
 | 
						|
 | 
						|
        if rv is NotImplemented:
 | 
						|
            rv = (str(type(self.obj)), id(self.obj)) < \
 | 
						|
                 (str(type(other.obj)), id(other.obj))
 | 
						|
        return rv
 | 
						|
 | 
						|
def _safe_tuple(t):
 | 
						|
    "Helper function for comparing 2-tuples"
 | 
						|
    return _safe_key(t[0]), _safe_key(t[1])
 | 
						|
 | 
						|
class PrettyPrinter:
 | 
						|
    def __init__(self, indent=1, width=80, depth=None, stream=None, *,
 | 
						|
                 compact=False):
 | 
						|
        """Handle pretty printing operations onto a stream using a set of
 | 
						|
        configured parameters.
 | 
						|
 | 
						|
        indent
 | 
						|
            Number of spaces to indent for each level of nesting.
 | 
						|
 | 
						|
        width
 | 
						|
            Attempted maximum number of columns in the output.
 | 
						|
 | 
						|
        depth
 | 
						|
            The maximum depth to print out nested structures.
 | 
						|
 | 
						|
        stream
 | 
						|
            The desired output stream.  If omitted (or false), the standard
 | 
						|
            output stream available at construction will be used.
 | 
						|
 | 
						|
        compact
 | 
						|
            If true, several items will be combined in one line.
 | 
						|
 | 
						|
        """
 | 
						|
        indent = int(indent)
 | 
						|
        width = int(width)
 | 
						|
        if indent < 0:
 | 
						|
            raise ValueError('indent must be >= 0')
 | 
						|
        if depth is not None and depth <= 0:
 | 
						|
            raise ValueError('depth must be > 0')
 | 
						|
        if not width:
 | 
						|
            raise ValueError('width must be != 0')
 | 
						|
        self._depth = depth
 | 
						|
        self._indent_per_level = indent
 | 
						|
        self._width = width
 | 
						|
        if stream is not None:
 | 
						|
            self._stream = stream
 | 
						|
        else:
 | 
						|
            self._stream = _sys.stdout
 | 
						|
        self._compact = bool(compact)
 | 
						|
 | 
						|
    def pprint(self, object):
 | 
						|
        self._format(object, self._stream, 0, 0, {}, 0)
 | 
						|
        self._stream.write("\n")
 | 
						|
 | 
						|
    def pformat(self, object):
 | 
						|
        sio = _StringIO()
 | 
						|
        self._format(object, sio, 0, 0, {}, 0)
 | 
						|
        return sio.getvalue()
 | 
						|
 | 
						|
    def isrecursive(self, object):
 | 
						|
        return self.format(object, {}, 0, 0)[2]
 | 
						|
 | 
						|
    def isreadable(self, object):
 | 
						|
        s, readable, recursive = self.format(object, {}, 0, 0)
 | 
						|
        return readable and not recursive
 | 
						|
 | 
						|
    def _format(self, object, stream, indent, allowance, context, level):
 | 
						|
        objid = id(object)
 | 
						|
        if objid in context:
 | 
						|
            stream.write(_recursion(object))
 | 
						|
            self._recursive = True
 | 
						|
            self._readable = False
 | 
						|
            return
 | 
						|
        rep = self._repr(object, context, level)
 | 
						|
        max_width = self._width - indent - allowance
 | 
						|
        if len(rep) > max_width:
 | 
						|
            p = self._dispatch.get(type(object).__repr__, None)
 | 
						|
            if p is not None:
 | 
						|
                context[objid] = 1
 | 
						|
                p(self, object, stream, indent, allowance, context, level + 1)
 | 
						|
                del context[objid]
 | 
						|
                return
 | 
						|
            elif isinstance(object, dict):
 | 
						|
                context[objid] = 1
 | 
						|
                self._pprint_dict(object, stream, indent, allowance,
 | 
						|
                                  context, level + 1)
 | 
						|
                del context[objid]
 | 
						|
                return
 | 
						|
        stream.write(rep)
 | 
						|
 | 
						|
    _dispatch = {}
 | 
						|
 | 
						|
    def _pprint_dict(self, object, stream, indent, allowance, context, level):
 | 
						|
        write = stream.write
 | 
						|
        write('{')
 | 
						|
        if self._indent_per_level > 1:
 | 
						|
            write((self._indent_per_level - 1) * ' ')
 | 
						|
        length = len(object)
 | 
						|
        if length:
 | 
						|
            items = sorted(object.items(), key=_safe_tuple)
 | 
						|
            self._format_dict_items(items, stream, indent, allowance + 1,
 | 
						|
                                    context, level)
 | 
						|
        write('}')
 | 
						|
 | 
						|
    _dispatch[dict.__repr__] = _pprint_dict
 | 
						|
 | 
						|
    def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level):
 | 
						|
        if not len(object):
 | 
						|
            stream.write(repr(object))
 | 
						|
            return
 | 
						|
        cls = object.__class__
 | 
						|
        stream.write(cls.__name__ + '(')
 | 
						|
        self._format(list(object.items()), stream,
 | 
						|
                     indent + len(cls.__name__) + 1, allowance + 1,
 | 
						|
                     context, level)
 | 
						|
        stream.write(')')
 | 
						|
 | 
						|
    _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
 | 
						|
 | 
						|
    def _pprint_list(self, object, stream, indent, allowance, context, level):
 | 
						|
        stream.write('[')
 | 
						|
        self._format_items(object, stream, indent, allowance + 1,
 | 
						|
                           context, level)
 | 
						|
        stream.write(']')
 | 
						|
 | 
						|
    _dispatch[list.__repr__] = _pprint_list
 | 
						|
 | 
						|
    def _pprint_tuple(self, object, stream, indent, allowance, context, level):
 | 
						|
        stream.write('(')
 | 
						|
        endchar = ',)' if len(object) == 1 else ')'
 | 
						|
        self._format_items(object, stream, indent, allowance + len(endchar),
 | 
						|
                           context, level)
 | 
						|
        stream.write(endchar)
 | 
						|
 | 
						|
    _dispatch[tuple.__repr__] = _pprint_tuple
 | 
						|
 | 
						|
    def _pprint_set(self, object, stream, indent, allowance, context, level):
 | 
						|
        if not len(object):
 | 
						|
            stream.write(repr(object))
 | 
						|
            return
 | 
						|
        typ = object.__class__
 | 
						|
        if typ is set:
 | 
						|
            stream.write('{')
 | 
						|
            endchar = '}'
 | 
						|
        else:
 | 
						|
            stream.write(typ.__name__ + '({')
 | 
						|
            endchar = '})'
 | 
						|
            indent += len(typ.__name__) + 1
 | 
						|
        object = sorted(object, key=_safe_key)
 | 
						|
        self._format_items(object, stream, indent, allowance + len(endchar),
 | 
						|
                           context, level)
 | 
						|
        stream.write(endchar)
 | 
						|
 | 
						|
    _dispatch[set.__repr__] = _pprint_set
 | 
						|
    _dispatch[frozenset.__repr__] = _pprint_set
 | 
						|
 | 
						|
    def _pprint_str(self, object, stream, indent, allowance, context, level):
 | 
						|
        write = stream.write
 | 
						|
        if not len(object):
 | 
						|
            write(repr(object))
 | 
						|
            return
 | 
						|
        chunks = []
 | 
						|
        lines = object.splitlines(True)
 | 
						|
        if level == 1:
 | 
						|
            indent += 1
 | 
						|
            allowance += 1
 | 
						|
        max_width1 = max_width = self._width - indent
 | 
						|
        for i, line in enumerate(lines):
 | 
						|
            rep = repr(line)
 | 
						|
            if i == len(lines) - 1:
 | 
						|
                max_width1 -= allowance
 | 
						|
            if len(rep) <= max_width1:
 | 
						|
                chunks.append(rep)
 | 
						|
            else:
 | 
						|
                # A list of alternating (non-space, space) strings
 | 
						|
                parts = re.findall(r'\S*\s*', line)
 | 
						|
                assert parts
 | 
						|
                assert not parts[-1]
 | 
						|
                parts.pop()  # drop empty last part
 | 
						|
                max_width2 = max_width
 | 
						|
                current = ''
 | 
						|
                for j, part in enumerate(parts):
 | 
						|
                    candidate = current + part
 | 
						|
                    if j == len(parts) - 1 and i == len(lines) - 1:
 | 
						|
                        max_width2 -= allowance
 | 
						|
                    if len(repr(candidate)) > max_width2:
 | 
						|
                        if current:
 | 
						|
                            chunks.append(repr(current))
 | 
						|
                        current = part
 | 
						|
                    else:
 | 
						|
                        current = candidate
 | 
						|
                if current:
 | 
						|
                    chunks.append(repr(current))
 | 
						|
        if len(chunks) == 1:
 | 
						|
            write(rep)
 | 
						|
            return
 | 
						|
        if level == 1:
 | 
						|
            write('(')
 | 
						|
        for i, rep in enumerate(chunks):
 | 
						|
            if i > 0:
 | 
						|
                write('\n' + ' '*indent)
 | 
						|
            write(rep)
 | 
						|
        if level == 1:
 | 
						|
            write(')')
 | 
						|
 | 
						|
    _dispatch[str.__repr__] = _pprint_str
 | 
						|
 | 
						|
    def _pprint_bytes(self, object, stream, indent, allowance, context, level):
 | 
						|
        write = stream.write
 | 
						|
        if len(object) <= 4:
 | 
						|
            write(repr(object))
 | 
						|
            return
 | 
						|
        parens = level == 1
 | 
						|
        if parens:
 | 
						|
            indent += 1
 | 
						|
            allowance += 1
 | 
						|
            write('(')
 | 
						|
        delim = ''
 | 
						|
        for rep in _wrap_bytes_repr(object, self._width - indent, allowance):
 | 
						|
            write(delim)
 | 
						|
            write(rep)
 | 
						|
            if not delim:
 | 
						|
                delim = '\n' + ' '*indent
 | 
						|
        if parens:
 | 
						|
            write(')')
 | 
						|
 | 
						|
    _dispatch[bytes.__repr__] = _pprint_bytes
 | 
						|
 | 
						|
    def _pprint_bytearray(self, object, stream, indent, allowance, context, level):
 | 
						|
        write = stream.write
 | 
						|
        write('bytearray(')
 | 
						|
        self._pprint_bytes(bytes(object), stream, indent + 10,
 | 
						|
                           allowance + 1, context, level + 1)
 | 
						|
        write(')')
 | 
						|
 | 
						|
    _dispatch[bytearray.__repr__] = _pprint_bytearray
 | 
						|
 | 
						|
    def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level):
 | 
						|
        stream.write('mappingproxy(')
 | 
						|
        self._format(object.copy(), stream, indent + 13, allowance + 1,
 | 
						|
                     context, level)
 | 
						|
        stream.write(')')
 | 
						|
 | 
						|
    _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy
 | 
						|
 | 
						|
    def _format_dict_items(self, items, stream, indent, allowance, context,
 | 
						|
                           level):
 | 
						|
        write = stream.write
 | 
						|
        indent += self._indent_per_level
 | 
						|
        delimnl = ',\n' + ' ' * indent
 | 
						|
        last_index = len(items) - 1
 | 
						|
        for i, (key, ent) in enumerate(items):
 | 
						|
            last = i == last_index
 | 
						|
            rep = self._repr(key, context, level)
 | 
						|
            write(rep)
 | 
						|
            write(': ')
 | 
						|
            self._format(ent, stream, indent + len(rep) + 2,
 | 
						|
                         allowance if last else 1,
 | 
						|
                         context, level)
 | 
						|
            if not last:
 | 
						|
                write(delimnl)
 | 
						|
 | 
						|
    def _format_items(self, items, stream, indent, allowance, context, level):
 | 
						|
        write = stream.write
 | 
						|
        indent += self._indent_per_level
 | 
						|
        if self._indent_per_level > 1:
 | 
						|
            write((self._indent_per_level - 1) * ' ')
 | 
						|
        delimnl = ',\n' + ' ' * indent
 | 
						|
        delim = ''
 | 
						|
        width = max_width = self._width - indent + 1
 | 
						|
        it = iter(items)
 | 
						|
        try:
 | 
						|
            next_ent = next(it)
 | 
						|
        except StopIteration:
 | 
						|
            return
 | 
						|
        last = False
 | 
						|
        while not last:
 | 
						|
            ent = next_ent
 | 
						|
            try:
 | 
						|
                next_ent = next(it)
 | 
						|
            except StopIteration:
 | 
						|
                last = True
 | 
						|
                max_width -= allowance
 | 
						|
                width -= allowance
 | 
						|
            if self._compact:
 | 
						|
                rep = self._repr(ent, context, level)
 | 
						|
                w = len(rep) + 2
 | 
						|
                if width < w:
 | 
						|
                    width = max_width
 | 
						|
                    if delim:
 | 
						|
                        delim = delimnl
 | 
						|
                if width >= w:
 | 
						|
                    width -= w
 | 
						|
                    write(delim)
 | 
						|
                    delim = ', '
 | 
						|
                    write(rep)
 | 
						|
                    continue
 | 
						|
            write(delim)
 | 
						|
            delim = delimnl
 | 
						|
            self._format(ent, stream, indent,
 | 
						|
                         allowance if last else 1,
 | 
						|
                         context, level)
 | 
						|
 | 
						|
    def _repr(self, object, context, level):
 | 
						|
        repr, readable, recursive = self.format(object, context.copy(),
 | 
						|
                                                self._depth, level)
 | 
						|
        if not readable:
 | 
						|
            self._readable = False
 | 
						|
        if recursive:
 | 
						|
            self._recursive = True
 | 
						|
        return repr
 | 
						|
 | 
						|
    def format(self, object, context, maxlevels, level):
 | 
						|
        """Format object for a specific context, returning a string
 | 
						|
        and flags indicating whether the representation is 'readable'
 | 
						|
        and whether the object represents a recursive construct.
 | 
						|
        """
 | 
						|
        return _safe_repr(object, context, maxlevels, level)
 | 
						|
 | 
						|
 | 
						|
# Return triple (repr_string, isreadable, isrecursive).
 | 
						|
 | 
						|
def _safe_repr(object, context, maxlevels, level):
 | 
						|
    typ = type(object)
 | 
						|
    if typ is str:
 | 
						|
        if 'locale' not in _sys.modules:
 | 
						|
            return repr(object), True, False
 | 
						|
        if "'" in object and '"' not in object:
 | 
						|
            closure = '"'
 | 
						|
            quotes = {'"': '\\"'}
 | 
						|
        else:
 | 
						|
            closure = "'"
 | 
						|
            quotes = {"'": "\\'"}
 | 
						|
        qget = quotes.get
 | 
						|
        sio = _StringIO()
 | 
						|
        write = sio.write
 | 
						|
        for char in object:
 | 
						|
            if char.isalpha():
 | 
						|
                write(char)
 | 
						|
            else:
 | 
						|
                write(qget(char, repr(char)[1:-1]))
 | 
						|
        return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
 | 
						|
 | 
						|
    r = getattr(typ, "__repr__", None)
 | 
						|
    if issubclass(typ, dict) and r is dict.__repr__:
 | 
						|
        if not object:
 | 
						|
            return "{}", True, False
 | 
						|
        objid = id(object)
 | 
						|
        if maxlevels and level >= maxlevels:
 | 
						|
            return "{...}", False, objid in context
 | 
						|
        if objid in context:
 | 
						|
            return _recursion(object), False, True
 | 
						|
        context[objid] = 1
 | 
						|
        readable = True
 | 
						|
        recursive = False
 | 
						|
        components = []
 | 
						|
        append = components.append
 | 
						|
        level += 1
 | 
						|
        saferepr = _safe_repr
 | 
						|
        items = sorted(object.items(), key=_safe_tuple)
 | 
						|
        for k, v in items:
 | 
						|
            krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
 | 
						|
            vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
 | 
						|
            append("%s: %s" % (krepr, vrepr))
 | 
						|
            readable = readable and kreadable and vreadable
 | 
						|
            if krecur or vrecur:
 | 
						|
                recursive = True
 | 
						|
        del context[objid]
 | 
						|
        return "{%s}" % ", ".join(components), readable, recursive
 | 
						|
 | 
						|
    if (issubclass(typ, list) and r is list.__repr__) or \
 | 
						|
       (issubclass(typ, tuple) and r is tuple.__repr__):
 | 
						|
        if issubclass(typ, list):
 | 
						|
            if not object:
 | 
						|
                return "[]", True, False
 | 
						|
            format = "[%s]"
 | 
						|
        elif len(object) == 1:
 | 
						|
            format = "(%s,)"
 | 
						|
        else:
 | 
						|
            if not object:
 | 
						|
                return "()", True, False
 | 
						|
            format = "(%s)"
 | 
						|
        objid = id(object)
 | 
						|
        if maxlevels and level >= maxlevels:
 | 
						|
            return format % "...", False, objid in context
 | 
						|
        if objid in context:
 | 
						|
            return _recursion(object), False, True
 | 
						|
        context[objid] = 1
 | 
						|
        readable = True
 | 
						|
        recursive = False
 | 
						|
        components = []
 | 
						|
        append = components.append
 | 
						|
        level += 1
 | 
						|
        for o in object:
 | 
						|
            orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
 | 
						|
            append(orepr)
 | 
						|
            if not oreadable:
 | 
						|
                readable = False
 | 
						|
            if orecur:
 | 
						|
                recursive = True
 | 
						|
        del context[objid]
 | 
						|
        return format % ", ".join(components), readable, recursive
 | 
						|
 | 
						|
    rep = repr(object)
 | 
						|
    return rep, (rep and not rep.startswith('<')), False
 | 
						|
 | 
						|
 | 
						|
def _recursion(object):
 | 
						|
    return ("<Recursion on %s with id=%s>"
 | 
						|
            % (type(object).__name__, id(object)))
 | 
						|
 | 
						|
 | 
						|
def _perfcheck(object=None):
 | 
						|
    import time
 | 
						|
    if object is None:
 | 
						|
        object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
 | 
						|
    p = PrettyPrinter()
 | 
						|
    t1 = time.time()
 | 
						|
    _safe_repr(object, {}, None, 0)
 | 
						|
    t2 = time.time()
 | 
						|
    p.pformat(object)
 | 
						|
    t3 = time.time()
 | 
						|
    print("_safe_repr:", t2 - t1)
 | 
						|
    print("pformat:", t3 - t2)
 | 
						|
 | 
						|
def _wrap_bytes_repr(object, width, allowance):
 | 
						|
    current = b''
 | 
						|
    last = len(object) // 4 * 4
 | 
						|
    for i in range(0, len(object), 4):
 | 
						|
        part = object[i: i+4]
 | 
						|
        candidate = current + part
 | 
						|
        if i == last:
 | 
						|
            width -= allowance
 | 
						|
        if len(repr(candidate)) > width:
 | 
						|
            if current:
 | 
						|
                yield repr(current)
 | 
						|
            current = part
 | 
						|
        else:
 | 
						|
            current = candidate
 | 
						|
    if current:
 | 
						|
        yield repr(current)
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    _perfcheck()
 |