这是我一段时间前创建的递归漂亮打印机,用于检查我的项目数据结构。它能够处理任意对象,为常见数据类型提供有用的输出,并允许通过参数进行一些自定义。注释掉的行代表可能想要使用的替代方案。代码也可在Gist中获得:
def generate_pprint(obj, level_indent=" ", max_depth=None, verbose_output=True,
justify_output=True, prevent_loops=True, prevent_revisit=False,
explore_objects=True, excluded_ids=[], visited_ids=[],
path_ids=[], current_depth=0):
"""Recursively generates pretty print of arbitrary objects.
Recursively generates pretty print of contents of arbitrary objects. Contents
are represented as lines containing key-value pairs. Recursion may be affected
by various parameters (see below).
Arguments:
max_depth: Maximum allowed depth of recursion. If depth is exceeded,
recursion is stopped.
verbose_output: Produce verbose output. Adds some additional details for
certain data types
justify_output: Justify output. Produces output in block-like appearance
with equal spacing between keys and values.
prevent_loops: Detect and prevent recursion loops by keeping track of
already visited objects within current recursion path.
prevent_revisit: Detect and prevent revisiting of already visited objects.
While 'prevent_loops' prevents revisiting objects only
within one single recursion path, this prevents revisiting
objects globally across all recursion paths.
explore_objects: Explore (i.e. recurse into) arbitrary objects. If enabled,
arbitrary objects not matching base types are explored.
If disabled, only certain types of objects are explored
(tuple, list, dict, set/frozenset). Note that this does
not affect the initially provided object (which is always
explored).
excluded_ids: List of object IDs to exclude from exploration (i.e. re-
cursion). Recursion is stopped if object with matching
ID is encountered.
visited_ids, Internal variables used to control recursion flow, loop
path_ids, detection and revisit detection. Never provide or modify
current_depth: these!
Returns:
Generated pretty print output as list of lines (strings).
Raises:
TypeError: Object or value has unsupported type (should never occur)
AssertionError: Assertion failed, most likely exposing a bug (should never
occur)
"""
output = []
indent = level_indent * current_depth
if (prevent_loops == True):
if (id(obj) in path_ids):
output.append(indent + "<recursion loop detected>")
return output
path_ids.append(id(obj))
if (prevent_revisit == True):
if (id(obj) in visited_ids):
output.append(indent + "<item already visited>")
return output
visited_ids.append(id(obj))
if (max_depth != None and current_depth > max_depth):
output.append(indent + "<recursion limit reached>")
return output
if (id(obj) in excluded_ids):
output.append(indent + "<item is excluded>")
return output
if (isinstance(obj, dict)):
keys = obj.keys()
values = obj
elif (isinstance(obj, tuple) or isinstance(obj, list)):
keys = range(0, len(obj))
values = obj
elif (isinstance(obj, set) or isinstance(obj, frozenset)):
keys = range(0, len(obj))
values = [ item for item in obj ]
elif (isinstance(obj, object)):
keys = [ item for item in dir(obj) if (not item.startswith("_")) ]
values = { key: getattr(obj, key) for key in keys }
else:
raise TypeError("unsupported object type: '%s'" % type(obj))
kstmp1 = kstmp2 = "%s"
if (justify_output == True):
maxlen = 0
for key in keys:
klen = len(str(key))
if (klen > maxlen):
maxlen = klen
kstmp1 = "%-" + str(maxlen+3) + "s"
kstmp2 = "%-" + str(maxlen+1) + "s"
for key in keys:
value = values[key]
keystr = kstmp1 % ("'" + str(key) + "':") if (isinstance(obj, dict) and isinstance(key, str)) else kstmp2 % (str(key) + ":")
valstr = ""
exp_obj = False
if (isinstance(value, dict)):
valstr = "<dict, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<dict, %d items>" % len(value)
elif (isinstance(value, tuple)):
valstr = "<tuple, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<tuple, %d items>" % len(value)
elif (isinstance(value, list)):
valstr = "<list, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<list, %d items>" % len(value)
elif (isinstance(value, set)):
valstr = "<set, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<set, %d items>" % len(value)
elif (isinstance(value, frozenset)):
valstr = "<frozenset, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<frozenset, %d items>" % len(value)
elif (isinstance(value, range)):
valstr = "<range, start %d, stop %d, step %d>" % (value.start, value.stop, value.step) if (verbose_output == True) else "<range(%d,%d,%d)>" % (value.start, value.stop, value.step)
elif (isinstance(value, bytes)):
valstr = "<bytes, %d bytes>" % len(value)
elif (isinstance(value, bytearray)):
valstr = "<bytearray, %d bytes>" % len(value)
elif (isinstance(value, memoryview)):
valstr = "<memoryview, %d bytes, object %s>" % (len(value), type(value.obj).__name__) if (verbose_output == True) else "<memoryview, %d bytes>" % len(value)
elif (isinstance(value, bool)):
valstr = "%s" % value
elif (isinstance(value, int)):
valstr = "%d (0x%x)" % (value, value)
elif (isinstance(value, float)):
valstr = "%s" % str(value)
elif (isinstance(value, complex)):
valstr = "%s" % str(value)
elif (isinstance(value, str)):
valstr = "'%s'" % repr(value)[1:-1]
elif (value == None):
valstr = "None"
elif isinstance(value, type):
valstr = "<class '%s.%s'>" % (value.__module__, value.__name__) if (verbose_output == True) else "<class>"
elif (callable(value)):
valstr = "<callable, class '%s.%s'>" % (value.__class__.__module__, value.__class__.__name__) if (verbose_output == True) else "<callable>"
elif (isinstance(value, object)):
valstr = "<object, class '%s.%s'>" % (value.__class__.__module__, value.__class__.__name__) if (verbose_output == True) else "<object>"
if (explore_objects == True):
exp_obj = True
else:
raise TypeError("unsupported value type: '%s'" % type(value))
output.append(indent + keystr + " " + valstr)
if (isinstance(value, dict) or isinstance(value, tuple) or isinstance(value, list) or isinstance(value, set) or
isinstance(value, frozenset) or (isinstance(value, object) and exp_obj == True)):
output += generate_pprint(value, level_indent=level_indent, max_depth=max_depth, verbose_output=verbose_output,
justify_output=justify_output, prevent_loops=prevent_loops, prevent_revisit=prevent_revisit,
explore_objects=explore_objects, excluded_ids=excluded_ids, visited_ids=visited_ids,
path_ids=path_ids, current_depth=current_depth + 1)
if (prevent_loops == True):
assert len(path_ids) > 0 and path_ids[-1] == id(obj), "last item in list of path objects not existing or not matching object"
path_ids.pop()
return output
def print_pprint(*args, **kwargs):
output = generate_pprint(*args, **kwargs)
for line in output:
print(line)
if (__name__ == "__main__"):
import sys
print_pprint(sys)
__repr__
和/或__str__
方法呢? - jonrsharpePrettyPrinter
,覆盖pformat()
、isreadable(object)
和isrecursive(object)
方法。 - Sylvain Leroux