mirror of
https://github.com/python/cpython.git
synced 2024-11-25 02:44:06 +08:00
47b9ff6ba1
*ordering* between objects; there is only a default equality test (defined by an object being equal to itself only). Read the comment in object.c. The current implementation never uses a three-way comparison to compute a rich comparison, but it does use a rich comparison to compute a three-way comparison. I'm not quite done ripping out all the calls to PyObject_Compare/Cmp, or replacing tp_compare implementations with tp_richcompare implementations; but much of that has happened (to make most unit tests pass). The following tests still fail, because I need help deciding or understanding: test_codeop -- depends on comparing code objects test_datetime -- need Tim Peters' opinion test_marshal -- depends on comparing code objects test_mutants -- need help understanding it The problem with test_codeop and test_marshal is this: these tests compare two different code objects and expect them to be equal. Is that still a feature we'd like to support? I've temporarily removed the comparison and hash code from code objects, so they use the default (equality by pointer only) comparison. For the other two tests, run them to see for yourself. (There may be more failing test with "-u all".) A general problem with getting lots of these tests to pass is the reality that for object types that have a natural total ordering, implementing __cmp__ is much more convenient than implementing __eq__, __ne__, __lt__, and so on. Should we go back to allowing __cmp__ to provide a total ordering? Should we provide some other way to implement rich comparison with a single method override? Alex proposed a __key__() method; I've considered a __richcmp__() method. Or perhaps __cmp__() just shouldn't be killed off...
321 lines
11 KiB
Python
321 lines
11 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 sys as _sys
|
|
|
|
from cStringIO import StringIO as _StringIO
|
|
|
|
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
|
|
"PrettyPrinter"]
|
|
|
|
# cache these for faster access:
|
|
_commajoin = ", ".join
|
|
_id = id
|
|
_len = len
|
|
_type = type
|
|
|
|
|
|
def pprint(object, stream=None, indent=1, width=80, depth=None):
|
|
"""Pretty-print a Python object to a stream [default is sys.stdout]."""
|
|
printer = PrettyPrinter(
|
|
stream=stream, indent=indent, width=width, depth=depth)
|
|
printer.pprint(object)
|
|
|
|
def pformat(object, indent=1, width=80, depth=None):
|
|
"""Format a Python object into a pretty-printed representation."""
|
|
return PrettyPrinter(indent=indent, width=width, depth=depth).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 PrettyPrinter:
|
|
def __init__(self, indent=1, width=80, depth=None, stream=None):
|
|
"""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.
|
|
|
|
"""
|
|
indent = int(indent)
|
|
width = int(width)
|
|
assert indent >= 0, "indent must be >= 0"
|
|
assert depth is None or depth > 0, "depth must be > 0"
|
|
assert width, "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
|
|
|
|
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):
|
|
level = level + 1
|
|
objid = _id(object)
|
|
if objid in context:
|
|
stream.write(_recursion(object))
|
|
self._recursive = True
|
|
self._readable = False
|
|
return
|
|
rep = self._repr(object, context, level - 1)
|
|
typ = _type(object)
|
|
sepLines = _len(rep) > (self._width - 1 - indent - allowance)
|
|
write = stream.write
|
|
|
|
if sepLines:
|
|
r = getattr(typ, "__repr__", None)
|
|
if issubclass(typ, dict) and r is dict.__repr__:
|
|
write('{')
|
|
if self._indent_per_level > 1:
|
|
write((self._indent_per_level - 1) * ' ')
|
|
length = _len(object)
|
|
if length:
|
|
context[objid] = 1
|
|
indent = indent + self._indent_per_level
|
|
items = object.items()
|
|
items.sort()
|
|
key, ent = items[0]
|
|
rep = self._repr(key, context, level)
|
|
write(rep)
|
|
write(': ')
|
|
self._format(ent, stream, indent + _len(rep) + 2,
|
|
allowance + 1, context, level)
|
|
if length > 1:
|
|
for key, ent in items[1:]:
|
|
rep = self._repr(key, context, level)
|
|
write(',\n%s%s: ' % (' '*indent, rep))
|
|
self._format(ent, stream, indent + _len(rep) + 2,
|
|
allowance + 1, context, level)
|
|
indent = indent - self._indent_per_level
|
|
del context[objid]
|
|
write('}')
|
|
return
|
|
|
|
if (issubclass(typ, list) and r is list.__repr__) or \
|
|
(issubclass(typ, tuple) and r is tuple.__repr__):
|
|
if issubclass(typ, list):
|
|
write('[')
|
|
endchar = ']'
|
|
else:
|
|
write('(')
|
|
endchar = ')'
|
|
if self._indent_per_level > 1:
|
|
write((self._indent_per_level - 1) * ' ')
|
|
length = _len(object)
|
|
if length:
|
|
context[objid] = 1
|
|
indent = indent + self._indent_per_level
|
|
self._format(object[0], stream, indent, allowance + 1,
|
|
context, level)
|
|
if length > 1:
|
|
for ent in object[1:]:
|
|
write(',\n' + ' '*indent)
|
|
self._format(ent, stream, indent,
|
|
allowance + 1, context, level)
|
|
indent = indent - self._indent_per_level
|
|
del context[objid]
|
|
if issubclass(typ, tuple) and length == 1:
|
|
write(',')
|
|
write(endchar)
|
|
return
|
|
|
|
write(rep)
|
|
|
|
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 = object.items()
|
|
try:
|
|
items = sorted(items)
|
|
except TypeError:
|
|
items = sorted(items, key=lambda (k, v): (str(type(k)), k, v))
|
|
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}" % _commajoin(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 % _commajoin(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
|
|
|
|
if __name__ == "__main__":
|
|
_perfcheck()
|