Skip to content

Commit 968a063

Browse files
committed
pprint: implement depth recursion
1 parent 0edfd9e commit 968a063

1 file changed

Lines changed: 95 additions & 33 deletions

File tree

python-stdlib/pprint/pprint.py

Lines changed: 95 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
from io import StringIO as _StringIO
2-
from sys import stdout as _stdout
3-
from machine import const as _const
1+
import io as _io
2+
import sys as _sys
3+
4+
try:
5+
from micropython import const as _const
6+
except ImportError:
7+
_const = lambda x: x
48

59

610
class PrettyPrinter:
@@ -10,27 +14,34 @@ class PrettyPrinter:
1014
call. For parameter descriptions, look at pprint() documentation.
1115
"""
1216

13-
def __init__(self, indent=1, stream=None, sort_dicts=True):
14-
self.indent = indent
15-
self.stream = stream if stream else _stdout
16-
self.sort_dicts = sort_dicts
17+
def __init__(self, indent=1, depth=1, stream=None, sort_dicts=True):
18+
self._indent = indent
19+
self._depth = depth
20+
self._stream = stream if stream else _sys.stdout
21+
self._sort_dicts = sort_dicts
1722

1823
def pformat(self, obj):
1924
"""Call pformat() function with cached constructor values"""
20-
return pformat(obj, indent=self.indent, sort_dicts=self.sort_dicts)
25+
return pformat(obj, indent=self._indent, depth=self._depth, sort_dicts=self._sort_dicts)
2126

2227
def pprint(self, obj):
2328
"""Call pprint() function with cached constructor values"""
24-
pprint(obj, stream=self.stream, indent=self.indent, sort_dicts=self.sort_dicts)
29+
pprint(
30+
obj,
31+
stream=self._stream,
32+
indent=self._indent,
33+
depth=self._depth,
34+
sort_dicts=self._sort_dicts,
35+
)
2536

2637

27-
def pformat(obj, indent=1, sort_dicts=True):
38+
def pformat(obj, indent=1, depth=1, sort_dicts=True):
2839
"""Simply calls pprint() but capturing prints into a string and returning.
2940
3041
Does not add a newline to the end like pprint() does.
3142
"""
32-
stream = _StringIO()
33-
_pprint_impl(obj, stream=stream, indent=indent, sort_dicts=sort_dicts)
43+
stream = _io.StringIO()
44+
_pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent)
3445
return stream.getvalue()
3546

3647

@@ -39,7 +50,7 @@ def pp(obj, *args, sort_dicts=False, **kwargs):
3950
pprint(obj, *args, sort_dicts=sort_dicts, **kwargs)
4051

4152

42-
def pprint(obj, stream=None, indent=1, sort_dicts=True):
53+
def pprint(obj, stream=None, indent=1, depth=1, sort_dicts=True):
4354
"""Simple implementation of a pretty-printer.
4455
4556
For simplicity, this does not recurse down. It does not produce the same
@@ -49,13 +60,26 @@ def pprint(obj, stream=None, indent=1, sort_dicts=True):
4960
Only expands containers. Currently supported containers are set, list,
5061
dict, and tuple.
5162
63+
The depth parameter defaults to 1 which is different from the CPython
64+
implementation which defaults to None, meaning infinite recursion. This
65+
implementation is significantly simpler and does not have safety checks
66+
against recursive structures (i.e., structures that contain a copy of
67+
themselves somewhere) or costly overexpansion. This simplicity gains speed
68+
and memory efficiency at the potential cost of expanding too far. Increase
69+
the depth parameter cautiously.
70+
5271
stream
5372
The desired output stream. If omitted (or False), the standard output
5473
stream will be used.
5574
5675
indent
5776
Number of space to indent for each level of nesting.
5877
78+
depth
79+
The maximum depth to print out nested structures. Set to None for
80+
infinite depth. Warning: be careful of infinite depth as infinite
81+
recursion and overexpansion checks are not performed.
82+
5983
sort_dicts
6084
If true, dict keys are sorted. You must ensure that the dicts
6185
encountered can be sorted against each other (for example, int cannot
@@ -65,8 +89,8 @@ def pprint(obj, stream=None, indent=1, sort_dicts=True):
6589
mismatch
6690
"""
6791
if not stream:
68-
stream = _stdout
69-
_pprint_impl(obj, stream, indent, sort_dicts)
92+
stream = _sys.stdout
93+
_pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent)
7094
stream.write("\n") # end in a newline
7195

7296

@@ -85,16 +109,36 @@ def ppdir(obj, *args, hidden=False, **kwargs):
85109
# Private implementation
86110

87111

88-
def _pprint_impl(obj, stream, indent, sort_dicts):
112+
def _pprint_impl(obj, stream, indent, sort_dicts, maxlevel, level, indent_per_level):
89113
type_code = _container_type(obj)
90-
if type_code != _NONCONTAINER and len(obj) > 1:
114+
if (maxlevel is not None and level >= maxlevel) or type_code == _NONCONTAINER or not obj:
115+
# directly print noncontainers, empty containers, or if we have reached
116+
# the max level.
117+
stream.write(repr(obj))
118+
else:
119+
# expand the container
91120
if type_code & _SIMPLE:
92-
_pprint_simple_container(obj, type_code, stream, indent, sort_dicts)
121+
_pprint_simple_container(
122+
obj,
123+
type_code,
124+
stream,
125+
indent + indent_per_level,
126+
sort_dicts,
127+
maxlevel,
128+
level + 1,
129+
indent_per_level,
130+
)
93131
else: # type_code & _DICT:
94-
_pprint_dict(obj, type_code, stream, indent, sort_dicts)
95-
else:
96-
# directly print noncontainers or containers with one or fewer elements
97-
stream.write(repr(obj))
132+
_pprint_dict(
133+
obj,
134+
type_code,
135+
stream,
136+
indent + indent_per_level,
137+
sort_dicts,
138+
maxlevel,
139+
level + 1,
140+
indent_per_level,
141+
)
98142

99143

100144
_NONCONTAINER = _const(0)
@@ -118,37 +162,55 @@ def _container_type(obj):
118162
return _NONCONTAINER
119163

120164

121-
def _pprint_simple_container(obj, type_code, stream, indent, sort_dicts):
165+
def _pprint_simple_container(
166+
obj, type_code, stream, indent, sort_dicts, maxlevel, level, indent_per_level
167+
):
122168
if type_code & _LIST:
123169
chars = "[]"
124170
elif type_code & _TUPLE:
125171
chars = "()"
126172
else: # type_code & _SET:
127173
chars = "{}"
128174

129-
stream.write(chars[0] + " " * (indent - 1))
175+
indent_string = " " * indent
176+
177+
stream.write(chars[0])
178+
stream.write(" " * (indent_per_level - 1))
130179
first = True
131180
for item in obj:
132181
if not first:
133-
stream.write(",\n" + " " * indent)
182+
stream.write(",\n")
183+
stream.write(indent_string)
134184
first = False
135-
# TODO: would recurse here
136-
stream.write(repr(item))
185+
# recurse here
186+
_pprint_impl(item, stream, indent, sort_dicts, maxlevel, level, indent_per_level)
187+
if type_code & _TUPLE and len(obj) == 1:
188+
stream.write(",")
137189
stream.write(chars[1])
138190

139191

140-
def _pprint_dict(obj, type_code, stream, indent, sort_dicts):
141-
stream.write("{" + " " * (indent - 1))
192+
def _pprint_dict(obj, type_code, stream, indent, sort_dicts, maxlevel, level, indent_per_level):
193+
indent_string = " " * indent
194+
stream.write("{")
195+
stream.write(" " * (indent_per_level - 1))
142196
first = True
143197
items = obj.items()
144198
if sort_dicts:
145199
items = sorted(items, key=lambda x: x[0])
146200
for key, val in items:
147201
if not first:
148-
stream.write(",\n" + " " * indent)
202+
stream.write(",\n")
203+
stream.write(indent_string)
149204
first = False
150-
# TODO: would recurse here
151-
stream.write(repr(key))
205+
# python's implementation does not multiline keys, then offsets the
206+
# value by the key's length. They still do safety checks such as
207+
# recursiveness and checking that it can be printed within the
208+
# specified width, which we skip in this implementation.
209+
keystr = repr(key)
210+
stream.write(keystr)
152211
stream.write(": ")
153-
stream.write(repr(val))
212+
# recurse on the value, lined up to wrap in front of the colon.
213+
_pprint_impl(
214+
val, stream, indent + len(keystr) + 2, sort_dicts, maxlevel, level, indent_per_level
215+
)
154216
stream.write("}")

0 commit comments

Comments
 (0)