[2] | 1 | # Author: Fred L. Drake, Jr.
|
---|
| 2 | # fdrake@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
|
---|
[391] | 38 | import warnings
|
---|
[2] | 39 |
|
---|
[391] | 40 | try:
|
---|
| 41 | from cStringIO import StringIO as _StringIO
|
---|
| 42 | except ImportError:
|
---|
| 43 | from StringIO import StringIO as _StringIO
|
---|
[2] | 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 |
|
---|
[391] | 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 |
|
---|
[2] | 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
|
---|
[391] | 158 | items = _sorted(object.items())
|
---|
[2] | 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 = ']'
|
---|
[391] | 188 | elif issubclass(typ, tuple):
|
---|
| 189 | write('(')
|
---|
| 190 | endchar = ')'
|
---|
| 191 | else:
|
---|
[2] | 192 | if not length:
|
---|
[391] | 193 | write(rep)
|
---|
[2] | 194 | return
|
---|
[391] | 195 | write(typ.__name__)
|
---|
| 196 | write('([')
|
---|
[2] | 197 | endchar = '])'
|
---|
[391] | 198 | indent += len(typ.__name__) + 1
|
---|
| 199 | object = _sorted(object)
|
---|
[2] | 200 | if self._indent_per_level > 1 and sepLines:
|
---|
| 201 | write((self._indent_per_level - 1) * ' ')
|
---|
| 202 | if length:
|
---|
| 203 | context[objid] = 1
|
---|
| 204 | indent = indent + self._indent_per_level
|
---|
| 205 | self._format(object[0], stream, indent, allowance + 1,
|
---|
| 206 | context, level)
|
---|
| 207 | if length > 1:
|
---|
| 208 | for ent in object[1:]:
|
---|
| 209 | if sepLines:
|
---|
| 210 | write(',\n' + ' '*indent)
|
---|
| 211 | else:
|
---|
| 212 | write(', ')
|
---|
| 213 | self._format(ent, stream, indent,
|
---|
| 214 | allowance + 1, context, level)
|
---|
| 215 | indent = indent - self._indent_per_level
|
---|
| 216 | del context[objid]
|
---|
| 217 | if issubclass(typ, tuple) and length == 1:
|
---|
| 218 | write(',')
|
---|
| 219 | write(endchar)
|
---|
| 220 | return
|
---|
| 221 |
|
---|
| 222 | write(rep)
|
---|
| 223 |
|
---|
| 224 | def _repr(self, object, context, level):
|
---|
| 225 | repr, readable, recursive = self.format(object, context.copy(),
|
---|
| 226 | self._depth, level)
|
---|
| 227 | if not readable:
|
---|
| 228 | self._readable = False
|
---|
| 229 | if recursive:
|
---|
| 230 | self._recursive = True
|
---|
| 231 | return repr
|
---|
| 232 |
|
---|
| 233 | def format(self, object, context, maxlevels, level):
|
---|
| 234 | """Format object for a specific context, returning a string
|
---|
| 235 | and flags indicating whether the representation is 'readable'
|
---|
| 236 | and whether the object represents a recursive construct.
|
---|
| 237 | """
|
---|
| 238 | return _safe_repr(object, context, maxlevels, level)
|
---|
| 239 |
|
---|
| 240 |
|
---|
| 241 | # Return triple (repr_string, isreadable, isrecursive).
|
---|
| 242 |
|
---|
| 243 | def _safe_repr(object, context, maxlevels, level):
|
---|
| 244 | typ = _type(object)
|
---|
| 245 | if typ is str:
|
---|
| 246 | if 'locale' not in _sys.modules:
|
---|
| 247 | return repr(object), True, False
|
---|
| 248 | if "'" in object and '"' not in object:
|
---|
| 249 | closure = '"'
|
---|
| 250 | quotes = {'"': '\\"'}
|
---|
| 251 | else:
|
---|
| 252 | closure = "'"
|
---|
| 253 | quotes = {"'": "\\'"}
|
---|
| 254 | qget = quotes.get
|
---|
| 255 | sio = _StringIO()
|
---|
| 256 | write = sio.write
|
---|
| 257 | for char in object:
|
---|
| 258 | if char.isalpha():
|
---|
| 259 | write(char)
|
---|
| 260 | else:
|
---|
| 261 | write(qget(char, repr(char)[1:-1]))
|
---|
| 262 | return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False
|
---|
| 263 |
|
---|
| 264 | r = getattr(typ, "__repr__", None)
|
---|
| 265 | if issubclass(typ, dict) and r is dict.__repr__:
|
---|
| 266 | if not object:
|
---|
| 267 | return "{}", True, False
|
---|
| 268 | objid = _id(object)
|
---|
| 269 | if maxlevels and level >= maxlevels:
|
---|
| 270 | return "{...}", False, objid in context
|
---|
| 271 | if objid in context:
|
---|
| 272 | return _recursion(object), False, True
|
---|
| 273 | context[objid] = 1
|
---|
| 274 | readable = True
|
---|
| 275 | recursive = False
|
---|
| 276 | components = []
|
---|
| 277 | append = components.append
|
---|
| 278 | level += 1
|
---|
| 279 | saferepr = _safe_repr
|
---|
[391] | 280 | for k, v in _sorted(object.items()):
|
---|
[2] | 281 | krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
|
---|
| 282 | vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
|
---|
| 283 | append("%s: %s" % (krepr, vrepr))
|
---|
| 284 | readable = readable and kreadable and vreadable
|
---|
| 285 | if krecur or vrecur:
|
---|
| 286 | recursive = True
|
---|
| 287 | del context[objid]
|
---|
| 288 | return "{%s}" % _commajoin(components), readable, recursive
|
---|
| 289 |
|
---|
| 290 | if (issubclass(typ, list) and r is list.__repr__) or \
|
---|
| 291 | (issubclass(typ, tuple) and r is tuple.__repr__):
|
---|
| 292 | if issubclass(typ, list):
|
---|
| 293 | if not object:
|
---|
| 294 | return "[]", True, False
|
---|
| 295 | format = "[%s]"
|
---|
| 296 | elif _len(object) == 1:
|
---|
| 297 | format = "(%s,)"
|
---|
| 298 | else:
|
---|
| 299 | if not object:
|
---|
| 300 | return "()", True, False
|
---|
| 301 | format = "(%s)"
|
---|
| 302 | objid = _id(object)
|
---|
| 303 | if maxlevels and level >= maxlevels:
|
---|
| 304 | return format % "...", False, objid in context
|
---|
| 305 | if objid in context:
|
---|
| 306 | return _recursion(object), False, True
|
---|
| 307 | context[objid] = 1
|
---|
| 308 | readable = True
|
---|
| 309 | recursive = False
|
---|
| 310 | components = []
|
---|
| 311 | append = components.append
|
---|
| 312 | level += 1
|
---|
| 313 | for o in object:
|
---|
| 314 | orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
|
---|
| 315 | append(orepr)
|
---|
| 316 | if not oreadable:
|
---|
| 317 | readable = False
|
---|
| 318 | if orecur:
|
---|
| 319 | recursive = True
|
---|
| 320 | del context[objid]
|
---|
| 321 | return format % _commajoin(components), readable, recursive
|
---|
| 322 |
|
---|
| 323 | rep = repr(object)
|
---|
| 324 | return rep, (rep and not rep.startswith('<')), False
|
---|
| 325 |
|
---|
| 326 |
|
---|
| 327 | def _recursion(object):
|
---|
| 328 | return ("<Recursion on %s with id=%s>"
|
---|
| 329 | % (_type(object).__name__, _id(object)))
|
---|
| 330 |
|
---|
| 331 |
|
---|
| 332 | def _perfcheck(object=None):
|
---|
| 333 | import time
|
---|
| 334 | if object is None:
|
---|
| 335 | object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000
|
---|
| 336 | p = PrettyPrinter()
|
---|
| 337 | t1 = time.time()
|
---|
| 338 | _safe_repr(object, {}, None, 0)
|
---|
| 339 | t2 = time.time()
|
---|
| 340 | p.pformat(object)
|
---|
| 341 | t3 = time.time()
|
---|
| 342 | print "_safe_repr:", t2 - t1
|
---|
| 343 | print "pformat:", t3 - t2
|
---|
| 344 |
|
---|
| 345 | if __name__ == "__main__":
|
---|
| 346 | _perfcheck()
|
---|