Home | History | Annotate | Download | only in python2.7
      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 try:
     41     from cStringIO import StringIO as _StringIO
     42 except ImportError:
     43     from StringIO import StringIO as _StringIO
     44 
     45 __all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
     46            "PrettyPrinter"]
     47 
     48 # cache these for faster access:
     49 _commajoin = ", ".join
     50 _id = id
     51 _len = len
     52 _type = type
     53 
     54 
     55 def pprint(object, stream=None, indent=1, width=80, depth=None):
     56     """Pretty-print a Python object to a stream [default is sys.stdout]."""
     57     printer = PrettyPrinter(
     58         stream=stream, indent=indent, width=width, depth=depth)
     59     printer.pprint(object)
     60 
     61 def pformat(object, indent=1, width=80, depth=None):
     62     """Format a Python object into a pretty-printed representation."""
     63     return PrettyPrinter(indent=indent, width=width, depth=depth).pformat(object)
     64 
     65 def saferepr(object):
     66     """Version of repr() which can handle recursive data structures."""
     67     return _safe_repr(object, {}, None, 0)[0]
     68 
     69 def isreadable(object):
     70     """Determine if saferepr(object) is readable by eval()."""
     71     return _safe_repr(object, {}, None, 0)[1]
     72 
     73 def isrecursive(object):
     74     """Determine if object requires a recursive representation."""
     75     return _safe_repr(object, {}, None, 0)[2]
     76 
     77 def _sorted(iterable):
     78     with warnings.catch_warnings():
     79         if _sys.py3kwarning:
     80             warnings.filterwarnings("ignore", "comparing unequal types "
     81                                     "not supported", DeprecationWarning)
     82         return sorted(iterable)
     83 
     84 class PrettyPrinter:
     85     def __init__(self, indent=1, width=80, depth=None, stream=None):
     86         """Handle pretty printing operations onto a stream using a set of
     87         configured parameters.
     88 
     89         indent
     90             Number of spaces to indent for each level of nesting.
     91 
     92         width
     93             Attempted maximum number of columns in the output.
     94 
     95         depth
     96             The maximum depth to print out nested structures.
     97 
     98         stream
     99             The desired output stream.  If omitted (or false), the standard
    100             output stream available at construction will be used.
    101 
    102         """
    103         indent = int(indent)
    104         width = int(width)
    105         assert indent >= 0, "indent must be >= 0"
    106         assert depth is None or depth > 0, "depth must be > 0"
    107         assert width, "width must be != 0"
    108         self._depth = depth
    109         self._indent_per_level = indent
    110         self._width = width
    111         if stream is not None:
    112             self._stream = stream
    113         else:
    114             self._stream = _sys.stdout
    115 
    116     def pprint(self, object):
    117         self._format(object, self._stream, 0, 0, {}, 0)
    118         self._stream.write("\n")
    119 
    120     def pformat(self, object):
    121         sio = _StringIO()
    122         self._format(object, sio, 0, 0, {}, 0)
    123         return sio.getvalue()
    124 
    125     def isrecursive(self, object):
    126         return self.format(object, {}, 0, 0)[2]
    127 
    128     def isreadable(self, object):
    129         s, readable, recursive = self.format(object, {}, 0, 0)
    130         return readable and not recursive
    131 
    132     def _format(self, object, stream, indent, allowance, context, level):
    133         level = level + 1
    134         objid = _id(object)
    135         if objid in context:
    136             stream.write(_recursion(object))
    137             self._recursive = True
    138             self._readable = False
    139             return
    140         rep = self._repr(object, context, level - 1)
    141         typ = _type(object)
    142         sepLines = _len(rep) > (self._width - 1 - indent - allowance)
    143         write = stream.write
    144 
    145         if self._depth and level > self._depth:
    146             write(rep)
    147             return
    148 
    149         r = getattr(typ, "__repr__", None)
    150         if issubclass(typ, dict) and r is dict.__repr__:
    151             write('{')
    152             if self._indent_per_level > 1:
    153                 write((self._indent_per_level - 1) * ' ')
    154             length = _len(object)
    155             if length:
    156                 context[objid] = 1
    157                 indent = indent + self._indent_per_level
    158                 items = _sorted(object.items())
    159                 key, ent = items[0]
    160                 rep = self._repr(key, context, level)
    161                 write(rep)
    162                 write(': ')
    163                 self._format(ent, stream, indent + _len(rep) + 2,
    164                               allowance + 1, context, level)
    165                 if length > 1:
    166                     for key, ent in items[1:]:
    167                         rep = self._repr(key, context, level)
    168                         if sepLines:
    169                             write(',\n%s%s: ' % (' '*indent, rep))
    170                         else:
    171                             write(', %s: ' % rep)
    172                         self._format(ent, stream, indent + _len(rep) + 2,
    173                                       allowance + 1, context, level)
    174                 indent = indent - self._indent_per_level
    175                 del context[objid]
    176             write('}')
    177             return
    178 
    179         if ((issubclass(typ, list) and r is list.__repr__) or
    180             (issubclass(typ, tuple) and r is tuple.__repr__) or
    181             (issubclass(typ, set) and r is set.__repr__) or
    182             (issubclass(typ, frozenset) and r is frozenset.__repr__)
    183            ):
    184             length = _len(object)
    185             if issubclass(typ, list):
    186                 write('[')
    187                 endchar = ']'
    188             elif issubclass(typ, set):
    189                 if not length:
    190                     write('set()')
    191                     return
    192                 write('set([')
    193                 endchar = '])'
    194                 object = _sorted(object)
    195                 indent += 4
    196             elif issubclass(typ, frozenset):
    197                 if not length:
    198                     write('frozenset()')
    199                     return
    200                 write('frozenset([')
    201                 endchar = '])'
    202                 object = _sorted(object)
    203                 indent += 10
    204             else:
    205                 write('(')
    206                 endchar = ')'
    207             if self._indent_per_level > 1 and sepLines:
    208                 write((self._indent_per_level - 1) * ' ')
    209             if length:
    210                 context[objid] = 1
    211                 indent = indent + self._indent_per_level
    212                 self._format(object[0], stream, indent, allowance + 1,
    213                              context, level)
    214                 if length > 1:
    215                     for ent in object[1:]:
    216                         if sepLines:
    217                             write(',\n' + ' '*indent)
    218                         else:
    219                             write(', ')
    220                         self._format(ent, stream, indent,
    221                                       allowance + 1, context, level)
    222                 indent = indent - self._indent_per_level
    223                 del context[objid]
    224             if issubclass(typ, tuple) and length == 1:
    225                 write(',')
    226             write(endchar)
    227             return
    228 
    229         write(rep)
    230 
    231     def _repr(self, object, context, level):
    232         repr, readable, recursive = self.format(object, context.copy(),
    233                                                 self._depth, level)
    234         if not readable:
    235             self._readable = False
    236         if recursive:
    237             self._recursive = True
    238         return repr
    239 
    240     def format(self, object, context, maxlevels, level):
    241         """Format object for a specific context, returning a string
    242         and flags indicating whether the representation is 'readable'
    243         and whether the object represents a recursive construct.
    244         """
    245         return _safe_repr(object, context, maxlevels, level)
    246 
    247 
    248 # Return triple (repr_string, isreadable, isrecursive).
    249 
    250 def _safe_repr(object, context, maxlevels, level):
    251     typ = _type(object)
    252     if typ is str:
    253         if 'locale' not in _sys.modules:
    254             return repr(object), True, False
    255         if "'" in object and '"' not in object:
    256             closure = '"'
    257             quotes = {'"': '\\"'}
    258         else:
    259             closure = "'"
    260             quotes = {"'": "\\'"}
    261         qget = quotes.get
    262         sio = _StringIO()
    263         write = sio.write
    264         for char in object:
    265             if char.isalpha():
    266                 write(char)
    267             else:
    268                 write(qget(char, repr(char)[1:-1]))
    269         return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
    270 
    271     r = getattr(typ, "__repr__", None)
    272     if issubclass(typ, dict) and r is dict.__repr__:
    273         if not object:
    274             return "{}", True, False
    275         objid = _id(object)
    276         if maxlevels and level >= maxlevels:
    277             return "{...}", False, objid in context
    278         if objid in context:
    279             return _recursion(object), False, True
    280         context[objid] = 1
    281         readable = True
    282         recursive = False
    283         components = []
    284         append = components.append
    285         level += 1
    286         saferepr = _safe_repr
    287         for k, v in _sorted(object.items()):
    288             krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
    289             vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
    290             append("%s: %s" % (krepr, vrepr))
    291             readable = readable and kreadable and vreadable
    292             if krecur or vrecur:
    293                 recursive = True
    294         del context[objid]
    295         return "{%s}" % _commajoin(components), readable, recursive
    296 
    297     if (issubclass(typ, list) and r is list.__repr__) or \
    298        (issubclass(typ, tuple) and r is tuple.__repr__):
    299         if issubclass(typ, list):
    300             if not object:
    301                 return "[]", True, False
    302             format = "[%s]"
    303         elif _len(object) == 1:
    304             format = "(%s,)"
    305         else:
    306             if not object:
    307                 return "()", True, False
    308             format = "(%s)"
    309         objid = _id(object)
    310         if maxlevels and level >= maxlevels:
    311             return format % "...", False, objid in context
    312         if objid in context:
    313             return _recursion(object), False, True
    314         context[objid] = 1
    315         readable = True
    316         recursive = False
    317         components = []
    318         append = components.append
    319         level += 1
    320         for o in object:
    321             orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
    322             append(orepr)
    323             if not oreadable:
    324                 readable = False
    325             if orecur:
    326                 recursive = True
    327         del context[objid]
    328         return format % _commajoin(components), readable, recursive
    329 
    330     rep = repr(object)
    331     return rep, (rep and not rep.startswith('<')), False
    332 
    333 
    334 def _recursion(object):
    335     return ("<Recursion on %s with id=%s>"
    336             % (_type(object).__name__, _id(object)))
    337 
    338 
    339 def _perfcheck(object=None):
    340     import time
    341     if object is None:
    342         object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
    343     p = PrettyPrinter()
    344     t1 = time.time()
    345     _safe_repr(object, {}, None, 0)
    346     t2 = time.time()
    347     p.pformat(object)
    348     t3 = time.time()
    349     print "_safe_repr:", t2 - t1
    350     print "pformat:", t3 - t2
    351 
    352 if __name__ == "__main__":
    353     _perfcheck()
    354