Brute-force performance hackery; buys back about 20% of the time for

saferepr(), a bit less for pformat().
This commit is contained in:
Fred Drake 2001-11-01 17:50:38 +00:00
parent a765c120f6
commit 49cc01e552
1 changed files with 157 additions and 113 deletions

View File

@ -45,6 +45,14 @@ except ImportError:
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr", __all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
"PrettyPrinter"] "PrettyPrinter"]
# cache these for faster access:
_commajoin = ", ".join
_sys_modules = sys.modules
_id = id
_len = len
_type = type
def pprint(object, stream=None): def pprint(object, stream=None):
"""Pretty-print a Python object to a stream [default is sys.sydout].""" """Pretty-print a Python object to a stream [default is sys.sydout]."""
printer = PrettyPrinter(stream=stream) printer = PrettyPrinter(stream=stream)
@ -56,15 +64,15 @@ def pformat(object):
def saferepr(object): def saferepr(object):
"""Version of repr() which can handle recursive data structures.""" """Version of repr() which can handle recursive data structures."""
return _safe_repr(object, {})[0] return _safe_repr(object, {}, None, 0)[0]
def isreadable(object): def isreadable(object):
"""Determine if saferepr(object) is readable by eval().""" """Determine if saferepr(object) is readable by eval()."""
return _safe_repr(object, {})[1] return _safe_repr(object, {}, None, 0)[1]
def isrecursive(object): def isrecursive(object):
"""Determine if object requires a recursive representation.""" """Determine if object requires a recursive representation."""
return _safe_repr(object, {})[2] return _safe_repr(object, {}, None, 0)[2]
class PrettyPrinter: class PrettyPrinter:
def __init__(self, indent=1, width=80, depth=None, stream=None): def __init__(self, indent=1, width=80, depth=None, stream=None):
@ -108,73 +116,84 @@ class PrettyPrinter:
def isrecursive(self, object): def isrecursive(self, object):
self.__recursive = 0 self.__recursive = 0
self.pformat(object) self.__repr(object, {}, 0)
return self.__recursive return self.__recursive
def isreadable(self, object): def isreadable(self, object):
self.__recursive = 0 self.__recursive = 0
self.__readable = 1 self.__readable = 1
self.pformat(object) self.__repr(object, {}, 0)
return self.__readable and not self.__recursive return self.__readable and not self.__recursive
def __format(self, object, stream, indent, allowance, context, level): def __format(self, object, stream, indent, allowance, context, level):
level = level + 1 level = level + 1
if context.has_key(id(object)): objid = _id(object)
object = _Recursion(object) if objid in context:
stream.write(_recursion(object))
self.__recursive = 1 self.__recursive = 1
self.__readable = 0
return
rep = self.__repr(object, context, level - 1) rep = self.__repr(object, context, level - 1)
objid = id(object) typ = _type(object)
context[objid] = 1 sepLines = _len(rep) > (self.__width - 1 - indent - allowance)
typ = type(object) write = stream.write
sepLines = len(rep) > (self.__width - 1 - indent - allowance)
if sepLines and typ in (ListType, TupleType): if sepLines:
# Pretty-print the sequence. if typ is DictType:
stream.write((typ is ListType) and '[' or '(') write('{')
if self.__indent_per_level > 1: if self.__indent_per_level > 1:
stream.write((self.__indent_per_level - 1) * ' ') write((self.__indent_per_level - 1) * ' ')
length = len(object) length = _len(object)
if length: if length:
indent = indent + self.__indent_per_level context[objid] = 1
self.__format(object[0], stream, indent, allowance + 1, indent = indent + self.__indent_per_level
context, level) items = object.items()
if length > 1: items.sort()
for ent in object[1:]: key, ent = items[0]
stream.write(',\n' + ' '*indent) rep = self.__repr(key, context, level)
self.__format(ent, stream, indent, write(rep)
allowance + 1, context, level) write(': ')
indent = indent - self.__indent_per_level self.__format(ent, stream, indent + _len(rep) + 2,
if typ is TupleType and length == 1: allowance + 1, context, level)
stream.write(',') if length > 1:
stream.write(((typ is ListType) and ']') or ')') 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
elif sepLines and typ is DictType: if typ is ListType or typ is TupleType:
stream.write('{') if typ is ListType:
if self.__indent_per_level > 1: write('[')
stream.write((self.__indent_per_level - 1) * ' ') endchar = ']'
length = len(object) else:
if length: write('(')
indent = indent + self.__indent_per_level endchar = ')'
items = object.items() if self.__indent_per_level > 1:
items.sort() write((self.__indent_per_level - 1) * ' ')
key, ent = items[0] length = _len(object)
rep = self.__repr(key, context, level) + ': ' if length:
stream.write(rep) context[objid] = 1
self.__format(ent, stream, indent + len(rep), indent = indent + self.__indent_per_level
allowance + 1, context, level) self.__format(object[0], stream, indent, allowance + 1,
if len(items) > 1: context, level)
for key, ent in items[1:]: if length > 1:
rep = self.__repr(key, context, level) + ': ' for ent in object[1:]:
stream.write(',\n' + ' '*indent + rep) write(',\n' + ' '*indent)
self.__format(ent, stream, indent + len(rep), self.__format(ent, stream, indent,
allowance + 1, context, level) allowance + 1, context, level)
indent = indent - self.__indent_per_level indent = indent - self.__indent_per_level
stream.write('}') del context[objid]
if typ is TupleType and length == 1:
write(',')
write(endchar)
return
else: write(rep)
stream.write(rep)
del context[objid]
def __repr(self, object, context, level): def __repr(self, object, context, level):
repr, readable, recursive = _safe_repr(object, context, repr, readable, recursive = _safe_repr(object, context,
@ -187,16 +206,10 @@ class PrettyPrinter:
# Return triple (repr_string, isreadable, isrecursive). # Return triple (repr_string, isreadable, isrecursive).
_have_module = sys.modules.has_key def _safe_repr(object, context, maxlevels, level):
typ = _type(object)
def _safe_repr(object, context, maxlevels=None, level=0): if typ is StringType:
level += 1 if 'locale' not in _sys_modules:
typ = type(object)
if not (typ in (DictType, ListType, TupleType, StringType) and object):
rep = `object`
return rep, (rep and (rep[0] != '<')), 0
elif typ is StringType:
if not _have_module('locale'):
return `object`, 1, 0 return `object`, 1, 0
if "'" in object and '"' not in object: if "'" in object and '"' not in object:
closure = '"' closure = '"'
@ -204,63 +217,94 @@ def _safe_repr(object, context, maxlevels=None, level=0):
else: else:
closure = "'" closure = "'"
quotes = {"'": "\\'"} quotes = {"'": "\\'"}
qget = quotes.get
sio = StringIO() sio = StringIO()
write = sio.write
for char in object: for char in object:
if char.isalpha(): if char.isalpha():
sio.write(char) write(char)
else: else:
sio.write(quotes.get(char, `char`[1:-1])) write(qget(char, `char`[1:-1]))
return closure + sio.getvalue() + closure, 1, 0 return ("%s%s%s" % (closure, sio.getvalue(), closure)), 1, 0
if context.has_key(id(object)): if typ is DictType:
return `_Recursion(object)`, 0, 1 if not object:
objid = id(object) return "{}", 1, 0
context[objid] = 1 objid = _id(object)
if maxlevels and level > maxlevels:
readable = 1 return "{...}", 0, objid in context
recursive = 0 if objid in context:
startchar, endchar = {ListType: "[]", return _recursion(object), 0, 1
TupleType: "()", context[objid] = 1
DictType: "{}"}[typ] readable = 1
if maxlevels and level > maxlevels: recursive = 0
with_commas = "..."
readable = 0
elif typ is DictType:
components = [] components = []
append = components.append
level += 1
saferepr = _safe_repr
for k, v in object.iteritems(): for k, v in object.iteritems():
krepr, kreadable, krecur = _safe_repr(k, context, maxlevels, krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
level) vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
vrepr, vreadable, vrecur = _safe_repr(v, context, maxlevels, append("%s: %s" % (krepr, vrepr))
level)
components.append("%s: %s" % (krepr, vrepr))
readable = readable and kreadable and vreadable readable = readable and kreadable and vreadable
recursive = recursive or krecur or vrecur if krecur or vrecur:
with_commas = ", ".join(components) recursive = 1
del context[objid]
return "{%s}" % _commajoin(components), readable, recursive
else: # list or tuple if typ is ListType or typ is TupleType:
assert typ in (ListType, TupleType) if typ is ListType:
if not object:
return "[]", 1, 0
format = "[%s]"
elif _len(object) == 1:
format = "(%s,)"
else:
if not object:
return "()", 1, 0
format = "(%s)"
objid = _id(object)
if maxlevels and level > maxlevels:
return format % "...", 0, objid in context
if objid in context:
return _recursion(object), 0, 1
context[objid] = 1
readable = 1
recursive = 0
components = [] components = []
for element in object: append = components.append
subrepr, subreadable, subrecur = _safe_repr( level += 1
element, context, maxlevels, level) for o in object:
components.append(subrepr) orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
readable = readable and subreadable append(orepr)
recursive = recursive or subrecur if not oreadable:
if len(components) == 1 and typ is TupleType: readable = 0
components[0] += "," if orecur:
with_commas = ", ".join(components) recursive = 1
del context[objid]
return format % _commajoin(components), readable, recursive
rep = `object`
return rep, (rep and not rep.startswith('<')), 0
s = "%s%s%s" % (startchar, with_commas, endchar)
del context[objid]
return s, readable and not recursive, recursive
class _Recursion: def _recursion(object):
# represent a recursive relationship; really only used for the __repr__() return ("<Recursion on %s with id=%s>"
# method... % (_type(object).__name__, _id(object)))
def __init__(self, object):
self.__repr = "<Recursion on %s with id=%s>" \
% (type(object).__name__, id(object))
def __repr__(self):
return self.__repr 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()