Home | History | Annotate | Download | only in Lib
      1 #  Author:      Fred L. Drake, Jr.

      2 #               fdrake (at] acm.org

      3 #

      4 #  This is a simple little module I wrote to make life easier.  I didn't

      5 #  see anything quite like it in the library, though I may have overlooked

      6 #  something.  I wrote this when I was trying to read some heavily nested

      7 #  tuples with fairly non-descriptive content.  This is modeled very much

      8 #  after Lisp/Scheme - style pretty-printing of lists.  If you find it

      9 #  useful, thank small children who sleep at night.

     10 
     11 """Support to pretty-print lists, tuples, & dictionaries recursively.
     12 
     13 Very simple, but useful, especially in debugging data structures.
     14 
     15 Classes
     16 -------
     17 
     18 PrettyPrinter()
     19     Handle pretty-printing operations onto a stream using a configured
     20     set of formatting parameters.
     21 
     22 Functions
     23 ---------
     24 
     25 pformat()
     26     Format a Python object into a pretty-printed representation.
     27 
     28 pprint()
     29     Pretty-print a Python object to a stream [default is sys.stdout].
     30 
     31 saferepr()
     32     Generate a 'standard' repr()-like value, but protect against recursive
     33     data structures.
     34 
     35 """
     36 
     37 import sys as _sys
     38 import warnings
     39 
     40 from cStringIO import StringIO as _StringIO
     41 
     42 __all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
     43            "PrettyPrinter"]
     44 
     45 # cache these for faster access:

     46 _commajoin = ", ".join
     47 _id = id
     48 _len = len
     49 _type = type
     50 
     51 
     52 def pprint(object, stream=None, indent=1, width=80, depth=None):
     53     """Pretty-print a Python object to a stream [default is sys.stdout]."""
     54     printer = PrettyPrinter(
     55         stream=stream, indent=indent, width=width, depth=depth)
     56     printer.pprint(object)
     57 
     58 def pformat(object, indent=1, width=80, depth=None):
     59     """Format a Python object into a pretty-printed representation."""
     60     return PrettyPrinter(indent=indent, width=width, depth=depth).pformat(object)
     61 
     62 def saferepr(object):
     63     """Version of repr() which can handle recursive data structures."""
     64     return _safe_repr(object, {}, None, 0)[0]
     65 
     66 def isreadable(object):
     67     """Determine if saferepr(object) is readable by eval()."""
     68     return _safe_repr(object, {}, None, 0)[1]
     69 
     70 def isrecursive(object):
     71     """Determine if object requires a recursive representation."""
     72     return _safe_repr(object, {}, None, 0)[2]
     73 
     74 def _sorted(iterable):
     75     with warnings.catch_warnings():
     76         if _sys.py3kwarning:
     77             warnings.filterwarnings("ignore", "comparing unequal types "
     78                                     "not supported", DeprecationWarning)
     79         return sorted(iterable)
     80 
     81 class PrettyPrinter:
     82     def __init__(self, indent=1, width=80, depth=None, stream=None):
     83         """Handle pretty printing operations onto a stream using a set of
     84         configured parameters.
     85 
     86         indent
     87             Number of spaces to indent for each level of nesting.
     88 
     89         width
     90             Attempted maximum number of columns in the output.
     91 
     92         depth
     93             The maximum depth to print out nested structures.
     94 
     95         stream
     96             The desired output stream.  If omitted (or false), the standard
     97             output stream available at construction will be used.
     98 
     99         """
    100         indent = int(indent)
    101         width = int(width)
    102         assert indent >= 0, "indent must be >= 0"
    103         assert depth is None or depth > 0, "depth must be > 0"
    104         assert width, "width must be != 0"
    105         self._depth = depth
    106         self._indent_per_level = indent
    107         self._width = width
    108         if stream is not None:
    109             self._stream = stream
    110         else:
    111             self._stream = _sys.stdout
    112 
    113     def pprint(self, object):
    114         self._format(object, self._stream, 0, 0, {}, 0)
    115         self._stream.write("\n")
    116 
    117     def pformat(self, object):
    118         sio = _StringIO()
    119         self._format(object, sio, 0, 0, {}, 0)
    120         return sio.getvalue()
    121 
    122     def isrecursive(self, object):
    123         return self.format(object, {}, 0, 0)[2]
    124 
    125     def isreadable(self, object):
    126         s, readable, recursive = self.format(object, {}, 0, 0)
    127         return readable and not recursive
    128 
    129     def _format(self, object, stream, indent, allowance, context, level):
    130         level = level + 1
    131         objid = _id(object)
    132         if objid in context:
    133             stream.write(_recursion(object))
    134             self._recursive = True
    135             self._readable = False
    136             return
    137         rep = self._repr(object, context, level - 1)
    138         typ = _type(object)
    139         sepLines = _len(rep) > (self._width - 1 - indent - allowance)
    140         write = stream.write
    141 
    142         if self._depth and level > self._depth:
    143             write(rep)
    144             return
    145 
    146         r = getattr(typ, "__repr__", None)
    147         if issubclass(typ, dict) and r is dict.__repr__:
    148             write('{')
    149             if self._indent_per_level > 1:
    150                 write((self._indent_per_level - 1) * ' ')
    151             length = _len(object)
    152             if length:
    153                 context[objid] = 1
    154                 indent = indent + self._indent_per_level
    155                 items = _sorted(object.items())
    156                 key, ent = items[0]
    157                 rep = self._repr(key, context, level)
    158                 write(rep)
    159                 write(': ')
    160                 self._format(ent, stream, indent + _len(rep) + 2,
    161                               allowance + 1, context, level)
    162                 if length > 1:
    163                     for key, ent in items[1:]:
    164                         rep = self._repr(key, context, level)
    165                         if sepLines:
    166                             write(',\n%s%s: ' % (' '*indent, rep))
    167                         else:
    168                             write(', %s: ' % rep)
    169                         self._format(ent, stream, indent + _len(rep) + 2,
    170                                       allowance + 1, context, level)
    171                 indent = indent - self._indent_per_level
    172                 del context[objid]
    173             write('}')
    174             return
    175 
    176         if ((issubclass(typ, list) and r is list.__repr__) or
    177             (issubclass(typ, tuple) and r is tuple.__repr__) or
    178             (issubclass(typ, set) and r is set.__repr__) or
    179             (issubclass(typ, frozenset) and r is frozenset.__repr__)
    180            ):
    181             length = _len(object)
    182             if issubclass(typ, list):
    183                 write('[')
    184                 endchar = ']'
    185             elif issubclass(typ, set):
    186                 if not length:
    187                     write('set()')
    188                     return
    189                 write('set([')
    190                 endchar = '])'
    191                 object = _sorted(object)
    192                 indent += 4
    193             elif issubclass(typ, frozenset):
    194                 if not length:
    195                     write('frozenset()')
    196                     return
    197                 write('frozenset([')
    198                 endchar = '])'
    199                 object = _sorted(object)
    200                 indent += 10
    201             else:
    202                 write('(')
    203                 endchar = ')'
    204             if self._indent_per_level > 1 and sepLines:
    205                 write((self._indent_per_level - 1) * ' ')
    206             if length:
    207                 context[objid] = 1
    208                 indent = indent + self._indent_per_level
    209                 self._format(object[0], stream, indent, allowance + 1,
    210                              context, level)
    211                 if length > 1:
    212                     for ent in object[1:]:
    213                         if sepLines:
    214                             write(',\n' + ' '*indent)
    215                         else:
    216                             write(', ')
    217                         self._format(ent, stream, indent,
    218                                       allowance + 1, context, level)
    219                 indent = indent - self._indent_per_level
    220                 del context[objid]
    221             if issubclass(typ, tuple) and length == 1:
    222                 write(',')
    223             write(endchar)
    224             return
    225 
    226         write(rep)
    227 
    228     def _repr(self, object, context, level):
    229         repr, readable, recursive = self.format(object, context.copy(),
    230                                                 self._depth, level)
    231         if not readable:
    232             self._readable = False
    233         if recursive:
    234             self._recursive = True
    235         return repr
    236 
    237     def format(self, object, context, maxlevels, level):
    238         """Format object for a specific context, returning a string
    239         and flags indicating whether the representation is 'readable'
    240         and whether the object represents a recursive construct.
    241         """
    242         return _safe_repr(object, context, maxlevels, level)
    243 
    244 
    245 # Return triple (repr_string, isreadable, isrecursive).

    246 
    247 def _safe_repr(object, context, maxlevels, level):
    248     typ = _type(object)
    249     if typ is str:
    250         if 'locale' not in _sys.modules:
    251             return repr(object), True, False
    252         if "'" in object and '"' not in object:
    253             closure = '"'
    254             quotes = {'"': '\\"'}
    255         else:
    256             closure = "'"
    257             quotes = {"'": "\\'"}
    258         qget = quotes.get
    259         sio = _StringIO()
    260         write = sio.write
    261         for char in object:
    262             if char.isalpha():
    263                 write(char)
    264             else:
    265                 write(qget(char, repr(char)[1:-1]))
    266         return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
    267 
    268     r = getattr(typ, "__repr__", None)
    269     if issubclass(typ, dict) and r is dict.__repr__:
    270         if not object:
    271             return "{}", True, False
    272         objid = _id(object)
    273         if maxlevels and level >= maxlevels:
    274             return "{...}", False, objid in context
    275         if objid in context:
    276             return _recursion(object), False, True
    277         context[objid] = 1
    278         readable = True
    279         recursive = False
    280         components = []
    281         append = components.append
    282         level += 1
    283         saferepr = _safe_repr
    284         for k, v in _sorted(object.items()):
    285             krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
    286             vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
    287             append("%s: %s" % (krepr, vrepr))
    288             readable = readable and kreadable and vreadable
    289             if krecur or vrecur:
    290                 recursive = True
    291         del context[objid]
    292         return "{%s}" % _commajoin(components), readable, recursive
    293 
    294     if (issubclass(typ, list) and r is list.__repr__) or \
    295        (issubclass(typ, tuple) and r is tuple.__repr__):
    296         if issubclass(typ, list):
    297             if not object:
    298                 return "[]", True, False
    299             format = "[%s]"
    300         elif _len(object) == 1:
    301             format = "(%s,)"
    302         else:
    303             if not object:
    304                 return "()", True, False
    305             format = "(%s)"
    306         objid = _id(object)
    307         if maxlevels and level >= maxlevels:
    308             return format % "...", False, objid in context
    309         if objid in context:
    310             return _recursion(object), False, True
    311         context[objid] = 1
    312         readable = True
    313         recursive = False
    314         components = []
    315         append = components.append
    316         level += 1
    317         for o in object:
    318             orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
    319             append(orepr)
    320             if not oreadable:
    321                 readable = False
    322             if orecur:
    323                 recursive = True
    324         del context[objid]
    325         return format % _commajoin(components), readable, recursive
    326 
    327     rep = repr(object)
    328     return rep, (rep and not rep.startswith('<')), False
    329 
    330 
    331 def _recursion(object):
    332     return ("<Recursion on %s with id=%s>"
    333             % (_type(object).__name__, _id(object)))
    334 
    335 
    336 def _perfcheck(object=None):
    337     import time
    338     if object is None:
    339         object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
    340     p = PrettyPrinter()
    341     t1 = time.time()
    342     _safe_repr(object, {}, None, 0)
    343     t2 = time.time()
    344     p.pformat(object)
    345     t3 = time.time()
    346     print "_safe_repr:", t2 - t1
    347     print "pformat:", t3 - t2
    348 
    349 if __name__ == "__main__":
    350     _perfcheck()
    351