You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
469 lines
16 KiB
469 lines
16 KiB
from __future__ import print_function
|
|
|
|
import io
|
|
import os
|
|
import ast
|
|
import sys
|
|
import warnings
|
|
import unicodedata
|
|
from collections import defaultdict, Counter
|
|
|
|
__all__ = [
|
|
"pprint", "pformat", "isreadable", "isrecursive", "saferepr",
|
|
"PrettyPrinter",
|
|
]
|
|
|
|
PY3 = sys.version_info >= (3, 0, 0)
|
|
BytesType = bytes
|
|
TextType = str if PY3 else unicode
|
|
u_prefix = '' if PY3 else 'u'
|
|
if PY3:
|
|
_ascii = globals()["__builtins__"]["ascii"]
|
|
chr_to_ascii = lambda x: _ascii(x)[1:-1]
|
|
unichr = chr
|
|
else:
|
|
chr_to_ascii = lambda x: repr(x)[2:-1]
|
|
|
|
class TextIO(io.TextIOWrapper):
|
|
def __init__(self, encoding=None):
|
|
io.TextIOWrapper.__init__(self, io.BytesIO(), encoding=encoding)
|
|
|
|
def getvalue(self):
|
|
self.flush()
|
|
return self.buffer.getvalue().decode(self.encoding)
|
|
|
|
|
|
# pprintpp will make an attempt to print as many Unicode characters as is
|
|
# safely possible. It will use the character category along with this table to
|
|
# determine whether or not it is safe to print a character. In this context,
|
|
# "safety" is defined as "the character will appear visually distinct" -
|
|
# combining characters, spaces, and other things which could be visually
|
|
# ambiguous are repr'd, others will be printed. I made this table mostly by
|
|
# hand, mostly guessing, so please file bugs.
|
|
# Source: http://www.unicode.org/reports/tr44/#GC_Values_Table
|
|
unicode_printable_categories = {
|
|
"Lu": 1, # Uppercase_Letter an uppercase letter
|
|
"Ll": 1, # Lowercase_Letter a lowercase letter
|
|
"Lt": 1, # Titlecase_Letter a digraphic character, with first part uppercase
|
|
"LC": 1, # Cased_Letter Lu | Ll | Lt
|
|
"Lm": 0, # Modifier_Letter a modifier letter
|
|
"Lo": 1, # Other_Letter other letters, including syllables and ideographs
|
|
"L": 1, # Letter Lu | Ll | Lt | Lm | Lo
|
|
"Mn": 0, # Nonspacing_Mark a nonspacing combining mark (zero advance width)
|
|
"Mc": 0, # Spacing_Mark a spacing combining mark (positive advance width)
|
|
"Me": 0, # Enclosing_Mark an enclosing combining mark
|
|
"M": 1, # Mark Mn | Mc | Me
|
|
"Nd": 1, # Decimal_Number a decimal digit
|
|
"Nl": 1, # Letter_Number a letterlike numeric character
|
|
"No": 1, # Other_Number a numeric character of other type
|
|
"N": 1, # Number Nd | Nl | No
|
|
"Pc": 1, # Connector_Punctuation a connecting punctuation mark, like a tie
|
|
"Pd": 1, # Dash_Punctuation a dash or hyphen punctuation mark
|
|
"Ps": 1, # Open_Punctuation an opening punctuation mark (of a pair)
|
|
"Pe": 1, # Close_Punctuation a closing punctuation mark (of a pair)
|
|
"Pi": 1, # Initial_Punctuation an initial quotation mark
|
|
"Pf": 1, # Final_Punctuation a final quotation mark
|
|
"Po": 1, # Other_Punctuation a punctuation mark of other type
|
|
"P": 1, # Punctuation Pc | Pd | Ps | Pe | Pi | Pf | Po
|
|
"Sm": 1, # Math_Symbol a symbol of mathematical use
|
|
"Sc": 1, # Currency_Symbol a currency sign
|
|
"Sk": 1, # Modifier_Symbol a non-letterlike modifier symbol
|
|
"So": 1, # Other_Symbol a symbol of other type
|
|
"S": 1, # Symbol Sm | Sc | Sk | So
|
|
"Zs": 0, # Space_Separator a space character (of various non-zero widths)
|
|
"Zl": 0, # Line_Separator U+2028 LINE SEPARATOR only
|
|
"Zp": 0, # Paragraph_Separator U+2029 PARAGRAPH SEPARATOR only
|
|
"Z": 1, # Separator Zs | Zl | Zp
|
|
"Cc": 0, # Control a C0 or C1 control code
|
|
"Cf": 0, # Format a format control character
|
|
"Cs": 0, # Surrogate a surrogate code point
|
|
"Co": 0, # Private_Use a private-use character
|
|
"Cn": 0, # Unassigned a reserved unassigned code point or a noncharacter
|
|
"C": 0, # Other Cc | Cf | Cs | Co | Cn
|
|
}
|
|
|
|
ascii_table = dict(
|
|
(unichr(i), chr_to_ascii(unichr(i)))
|
|
for i in range(255)
|
|
)
|
|
|
|
def pprint(object, stream=None, indent=4, 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=4, 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 PrettyPrinter().pformat(object)
|
|
|
|
def isreadable(object):
|
|
"""Determine if saferepr(object) is readable by eval()."""
|
|
return PrettyPrinter().isreadable(object)
|
|
|
|
def isrecursive(object):
|
|
"""Determine if object requires a recursive representation."""
|
|
return PrettyPrinter().isrecursive(object)
|
|
|
|
def _sorted(iterable):
|
|
with warnings.catch_warnings():
|
|
if getattr(sys, "py3kwarning", False):
|
|
warnings.filterwarnings("ignore", "comparing unequal types "
|
|
"not supported", DeprecationWarning)
|
|
return sorted(iterable)
|
|
|
|
def console(argv=None):
|
|
if argv is None:
|
|
argv = sys.argv
|
|
if len(argv) != 1:
|
|
name = argv[0]
|
|
if name.startswith("/"):
|
|
name = os.path.basename(name)
|
|
print("Usage: %s" %(argv[0], ))
|
|
print()
|
|
print("Pipe Python literals into %s to pretty-print them" %(argv[0], ))
|
|
return 1
|
|
obj = ast.literal_eval(sys.stdin.read().strip())
|
|
pprint(obj)
|
|
return 0
|
|
|
|
def monkeypatch(mod=None):
|
|
if "pprint" in sys.modules:
|
|
warnings.warn("'pprint' has already been imported; monkeypatching "
|
|
"won't work everywhere.")
|
|
import pprint
|
|
sys.modules["pprint_original"] = pprint
|
|
sys.modules["pprint"] = mod or sys.modules["pprintpp"]
|
|
|
|
class PPrintSharedState(object):
|
|
recursive = False
|
|
readable = True
|
|
cur_line_length = 0
|
|
|
|
def clone(self):
|
|
new = type(self)()
|
|
new.__dict__.update(self.__dict__)
|
|
return new
|
|
|
|
|
|
class PPrintState(object):
|
|
indent = 4
|
|
level = 0
|
|
max_width = 80
|
|
max_depth = None
|
|
stream = None
|
|
context = None
|
|
write_constrain = None
|
|
|
|
class WriteConstrained(Exception):
|
|
pass
|
|
|
|
def __init__(self, **attrs):
|
|
self.__dict__.update(attrs)
|
|
self.s = PPrintSharedState()
|
|
|
|
def assert_sanity(self):
|
|
assert self.indent >= 0, "indent must be >= 0"
|
|
assert self.max_depth is None or self.max_depth > 0, "depth must be > 0"
|
|
assert self.max_width, "width must be != 0"
|
|
|
|
def replace(self, **attrs):
|
|
new_state = type(self)()
|
|
new_state.__dict__.update(self.__dict__)
|
|
new_state.__dict__.update(attrs)
|
|
new_state.context = dict(new_state.context)
|
|
new_state.s = self.s
|
|
return new_state
|
|
|
|
def clone(self, clone_shared=False):
|
|
new = self.replace()
|
|
if clone_shared:
|
|
new.s = self.s.clone()
|
|
return new
|
|
|
|
def write(self, data):
|
|
if self.write_constrain is not None:
|
|
self.write_constrain -= len(data)
|
|
if self.write_constrain < 0:
|
|
raise self.WriteConstrained
|
|
|
|
if isinstance(data, BytesType):
|
|
data = data.decode("ascii")
|
|
self.stream.write(data)
|
|
nl_idx = data.rfind("\n")
|
|
if nl_idx < 0:
|
|
self.s.cur_line_length += len(data)
|
|
else:
|
|
self.s.cur_line_length = len(data) - (nl_idx + 1)
|
|
|
|
def get_indent_string(self):
|
|
return (self.level * self.indent) * " "
|
|
|
|
|
|
class PrettyPrinter(object):
|
|
def __init__(self, indent=4, 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.
|
|
|
|
"""
|
|
self.get_default_state = lambda: PPrintState(
|
|
indent=int(indent),
|
|
max_width=int(width),
|
|
stream=stream or sys.stdout,
|
|
context={},
|
|
)
|
|
self.get_default_state().assert_sanity()
|
|
|
|
def pprint(self, object, state=None):
|
|
state = state or self.get_default_state()
|
|
self._format(object, state)
|
|
state.write("\n")
|
|
|
|
def pformat(self, object, state=None):
|
|
sio = TextIO()
|
|
state = state or self.get_default_state()
|
|
state = state.replace(stream=sio)
|
|
self._format(object, state)
|
|
return sio.getvalue()
|
|
|
|
def isrecursive(self, object):
|
|
state = self.get_default_state()
|
|
self._format(object, state)
|
|
return state.s.recursive
|
|
|
|
def isreadable(self, object):
|
|
state = self.get_default_state()
|
|
self._format(object, state)
|
|
return state.s.readable and not state.s.recursive
|
|
|
|
_container_reprs = {
|
|
dict.__repr__: ("dict", "{", "}", "{}"),
|
|
list.__repr__: ("list", "[", "]", "[]"),
|
|
tuple.__repr__: ("tuple", "(", ")", "()"),
|
|
set.__repr__: ("set", "set([", "])", "set()"),
|
|
frozenset.__repr__: ("set", "frozenset([", "])", "frozenset()"),
|
|
Counter.__repr__: ("dict", "Counter({", "})", "Counter()"),
|
|
defaultdict.__repr__: ("dict", None, "})", None),
|
|
}
|
|
|
|
def _format_nested_objects(self, object, state, typeish=None):
|
|
objid = id(object)
|
|
state.level += 1
|
|
state.context[objid] = 1
|
|
try:
|
|
# First, try to fit everything on one line. For simplicity, assume
|
|
# that it takes three characters to close the object (ex, `]),`)
|
|
oneline_state = state.clone(clone_shared=True)
|
|
oneline_state.stream = TextIO()
|
|
oneline_state.write_constrain = (
|
|
state.max_width - state.s.cur_line_length - 3
|
|
)
|
|
try:
|
|
self._write_nested_real(object, oneline_state, typeish,
|
|
oneline=True)
|
|
oneline_value = oneline_state.stream.getvalue()
|
|
if "\n" in oneline_value:
|
|
oneline_value = None
|
|
except oneline_state.WriteConstrained:
|
|
oneline_value = None
|
|
if oneline_value is not None:
|
|
state.write(oneline_value)
|
|
return
|
|
state.write("\n" + state.get_indent_string())
|
|
self._write_nested_real(object, state, typeish)
|
|
finally:
|
|
state.level -= 1
|
|
state.write(state.get_indent_string())
|
|
|
|
def _write_nested_real(self, object, state, typeish, oneline=False):
|
|
indent_str = state.get_indent_string()
|
|
first = True
|
|
joiner = oneline and ", " or ",\n" + indent_str
|
|
if typeish == "dict":
|
|
for k, v in _sorted(object.items()):
|
|
if first:
|
|
first = False
|
|
else:
|
|
state.write(joiner)
|
|
self._format(k, state)
|
|
state.write(": ")
|
|
self._format(v, state)
|
|
else:
|
|
if typeish == "set":
|
|
object = _sorted(object)
|
|
for o in object:
|
|
if first:
|
|
first = False
|
|
else:
|
|
state.write(joiner)
|
|
self._format(o, state)
|
|
if oneline and typeish == "tuple" and len(object) == 1:
|
|
state.write(", ")
|
|
elif not oneline:
|
|
state.write(",\n")
|
|
|
|
def _format(self, object, state):
|
|
write = state.write
|
|
if state.max_depth and state.level >= state.max_depth:
|
|
write("...")
|
|
return
|
|
state = state.clone()
|
|
objid = id(object)
|
|
if objid in state.context:
|
|
write(self._recursion(object, state))
|
|
return
|
|
|
|
typ = type(object)
|
|
r = typ.__repr__
|
|
opener_closer_empty = self._container_reprs.get(r)
|
|
if opener_closer_empty is not None:
|
|
typeish, opener, closer, empty = opener_closer_empty
|
|
if r == defaultdict.__repr__:
|
|
factory_repr = object.default_factory
|
|
opener = "defaultdict(%r, {" %(factory_repr, )
|
|
empty = opener + closer
|
|
|
|
length = len(object)
|
|
if length == 0:
|
|
write(empty)
|
|
return
|
|
|
|
write(opener)
|
|
self._format_nested_objects(object, state, typeish=typeish)
|
|
write(closer)
|
|
return
|
|
|
|
if r == BytesType.__repr__:
|
|
write(repr(object))
|
|
return
|
|
|
|
if r == TextType.__repr__:
|
|
if "'" in object and '"' not in object:
|
|
quote = '"'
|
|
quotes = {'"': '\\"'}
|
|
else:
|
|
quote = "'"
|
|
quotes = {"'": "\\'"}
|
|
qget = quotes.get
|
|
ascii_table_get = ascii_table.get
|
|
unicat_get = unicodedata.category
|
|
write(u_prefix + quote)
|
|
for char in object:
|
|
if ord(char) > 0x7F:
|
|
cat = unicat_get(char)
|
|
if unicode_printable_categories.get(cat):
|
|
try:
|
|
write(char)
|
|
continue
|
|
except UnicodeEncodeError:
|
|
pass
|
|
write(
|
|
qget(char) or
|
|
ascii_table_get(char) or
|
|
chr_to_ascii(char)
|
|
)
|
|
write(quote)
|
|
return
|
|
|
|
orepr = repr(object)
|
|
orepr = orepr.replace("\n", "\n" + state.get_indent_string())
|
|
state.s.readable = (
|
|
state.s.readable and
|
|
not orepr.startswith("<")
|
|
)
|
|
write(orepr)
|
|
return
|
|
|
|
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.
|
|
"""
|
|
state = self.get_default_state()
|
|
result = self.pformat(object, state=state)
|
|
return result, state.s.readable, state.s.recursive
|
|
|
|
def _recursion(self, object, state):
|
|
state.s.recursive = True
|
|
return ("<Recursion on %s with id=%s>"
|
|
% (type(object).__name__, id(object)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
import numpy as np
|
|
except ImportError:
|
|
class np(object):
|
|
@staticmethod
|
|
def array(o):
|
|
return o
|
|
|
|
somelist = [1,2,3]
|
|
recursive = []
|
|
recursive.extend([recursive, recursive, recursive])
|
|
pprint({
|
|
"a": {"a": "b"},
|
|
"b": [somelist, somelist],
|
|
"c": [
|
|
(1, ),
|
|
(1,2,3),
|
|
],
|
|
"counter": [
|
|
Counter(),
|
|
Counter("asdfasdfasdf"),
|
|
],
|
|
"dd": [
|
|
defaultdict(int, {}),
|
|
defaultdict(int, {"foo": 42}),
|
|
],
|
|
"np": [
|
|
"hello",
|
|
#np.array([[1,2],[3,4]]),
|
|
"world",
|
|
],
|
|
u"u": ["a", u"\u1234", "b"],
|
|
"recursive": recursive,
|
|
"z": {
|
|
"very very very long key stuff 1234": {
|
|
"much value": "very nest! " * 10,
|
|
u"unicode": u"4U!'\"",
|
|
},
|
|
"aldksfj alskfj askfjas fkjasdlkf jasdlkf ajslfjas": ["asdf"] * 10,
|
|
},
|
|
})
|
|
pprint(u"\xe9e\u0301")
|
|
uni_safe = u"\xe9 \u6f02 \u0e4f \u2661"
|
|
uni_unsafe = u"\u200a \u0301 \n"
|
|
unistr = uni_safe + " --- " + uni_unsafe
|
|
sys.modules.pop("locale", None)
|
|
pprint(unistr)
|
|
stream = TextIO(encoding="ascii")
|
|
pprint(unistr, stream=stream)
|
|
print(stream.getvalue())
|