|
6 | 6 | from itertools import islice |
7 | 7 | from _thread import get_ident |
8 | 8 |
|
9 | | -def recursive_repr(fillvalue='...'): |
10 | | - 'Decorator to make a repr function return fillvalue for a recursive call' |
11 | | - |
12 | | - def decorating_function(user_function): |
13 | | - repr_running = set() |
14 | | - |
15 | | - def wrapper(self): |
16 | | - key = id(self), get_ident() |
17 | | - if key in repr_running: |
18 | | - return fillvalue |
19 | | - repr_running.add(key) |
20 | | - try: |
21 | | - result = user_function(self) |
22 | | - finally: |
23 | | - repr_running.discard(key) |
24 | | - return result |
25 | | - |
26 | | - # Can't use functools.wraps() here because of bootstrap issues |
27 | | - wrapper.__module__ = getattr(user_function, '__module__') |
28 | | - wrapper.__doc__ = getattr(user_function, '__doc__') |
29 | | - wrapper.__name__ = getattr(user_function, '__name__') |
30 | | - wrapper.__qualname__ = getattr(user_function, '__qualname__') |
31 | | - wrapper.__annotate__ = getattr(user_function, '__annotate__', None) |
32 | | - wrapper.__type_params__ = getattr(user_function, '__type_params__', ()) |
33 | | - wrapper.__wrapped__ = user_function |
34 | | - return wrapper |
35 | | - |
36 | | - return decorating_function |
37 | | - |
38 | | -class Repr: |
39 | | - _lookup = { |
40 | | - 'tuple': 'builtins', |
41 | | - 'list': 'builtins', |
42 | | - 'array': 'array', |
43 | | - 'set': 'builtins', |
44 | | - 'frozenset': 'builtins', |
45 | | - 'deque': 'collections', |
46 | | - 'dict': 'builtins', |
47 | | - 'str': 'builtins', |
48 | | - 'int': 'builtins' |
49 | | - } |
50 | | - |
51 | | - def __init__( |
52 | | - self, *, maxlevel=6, maxtuple=6, maxlist=6, maxarray=5, maxdict=4, |
53 | | - maxset=6, maxfrozenset=6, maxdeque=6, maxstring=30, maxlong=40, |
54 | | - maxother=30, fillvalue='...', indent=None, |
55 | | - ): |
56 | | - self.maxlevel = maxlevel |
57 | | - self.maxtuple = maxtuple |
58 | | - self.maxlist = maxlist |
59 | | - self.maxarray = maxarray |
60 | | - self.maxdict = maxdict |
61 | | - self.maxset = maxset |
62 | | - self.maxfrozenset = maxfrozenset |
63 | | - self.maxdeque = maxdeque |
64 | | - self.maxstring = maxstring |
65 | | - self.maxlong = maxlong |
66 | | - self.maxother = maxother |
67 | | - self.fillvalue = fillvalue |
68 | | - self.indent = indent |
69 | | - |
70 | | - def repr(self, x): |
71 | | - return self.repr1(x, self.maxlevel) |
72 | | - |
73 | | - def repr1(self, x, level): |
74 | | - cls = type(x) |
75 | | - typename = cls.__name__ |
76 | | - |
77 | | - if ' ' in typename: |
78 | | - parts = typename.split() |
79 | | - typename = '_'.join(parts) |
80 | | - |
81 | | - method = getattr(self, 'repr_' + typename, None) |
82 | | - if method: |
83 | | - # not defined in this class |
84 | | - if typename not in self._lookup: |
85 | | - return method(x, level) |
86 | | - module = getattr(cls, '__module__', None) |
87 | | - # defined in this class and is the module intended |
88 | | - if module == self._lookup[typename]: |
89 | | - return method(x, level) |
90 | | - |
91 | | - return self.repr_instance(x, level) |
92 | | - |
93 | | - def _join(self, pieces, level): |
94 | | - if self.indent is None: |
95 | | - return ', '.join(pieces) |
96 | | - if not pieces: |
97 | | - return '' |
98 | | - indent = self.indent |
99 | | - if isinstance(indent, int): |
100 | | - if indent < 0: |
101 | | - raise ValueError( |
102 | | - f'Repr.indent cannot be negative int (was {indent!r})' |
103 | | - ) |
104 | | - indent *= ' ' |
105 | | - try: |
106 | | - sep = ',\n' + (self.maxlevel - level + 1) * indent |
107 | | - except TypeError as error: |
108 | | - raise TypeError( |
109 | | - f'Repr.indent must be a str, int or None, not {type(indent)}' |
110 | | - ) from error |
111 | | - return sep.join(('', *pieces, ''))[1:-len(indent) or None] |
112 | | - |
113 | | - def _repr_iterable(self, x, level, left, right, maxiter, trail=''): |
114 | | - n = len(x) |
115 | | - if level <= 0 and n: |
116 | | - s = self.fillvalue |
117 | | - else: |
118 | | - newlevel = level - 1 |
119 | | - repr1 = self.repr1 |
120 | | - pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)] |
121 | | - if n > maxiter: |
122 | | - pieces.append(self.fillvalue) |
123 | | - s = self._join(pieces, level) |
124 | | - if n == 1 and trail and self.indent is None: |
125 | | - right = trail + right |
126 | | - return '%s%s%s' % (left, s, right) |
127 | | - |
128 | | - def repr_tuple(self, x, level): |
129 | | - return self._repr_iterable(x, level, '(', ')', self.maxtuple, ',') |
130 | | - |
131 | | - def repr_list(self, x, level): |
132 | | - return self._repr_iterable(x, level, '[', ']', self.maxlist) |
133 | | - |
134 | | - def repr_array(self, x, level): |
135 | | - if not x: |
136 | | - return "array('%s')" % x.typecode |
137 | | - header = "array('%s', [" % x.typecode |
138 | | - return self._repr_iterable(x, level, header, '])', self.maxarray) |
139 | | - |
140 | | - def repr_set(self, x, level): |
141 | | - if not x: |
142 | | - return 'set()' |
143 | | - x = _possibly_sorted(x) |
144 | | - return self._repr_iterable(x, level, '{', '}', self.maxset) |
145 | | - |
146 | | - def repr_frozenset(self, x, level): |
147 | | - if not x: |
148 | | - return 'frozenset()' |
149 | | - x = _possibly_sorted(x) |
150 | | - return self._repr_iterable(x, level, 'frozenset({', '})', |
151 | | - self.maxfrozenset) |
152 | | - |
153 | | - def repr_deque(self, x, level): |
154 | | - return self._repr_iterable(x, level, 'deque([', '])', self.maxdeque) |
155 | | - |
156 | | - def repr_dict(self, x, level): |
157 | | - n = len(x) |
158 | | - if n == 0: |
159 | | - return '{}' |
160 | | - if level <= 0: |
161 | | - return '{' + self.fillvalue + '}' |
162 | | - newlevel = level - 1 |
163 | | - repr1 = self.repr1 |
164 | | - pieces = [] |
165 | | - for key in islice(_possibly_sorted(x), self.maxdict): |
166 | | - keyrepr = repr1(key, newlevel) |
167 | | - valrepr = repr1(x[key], newlevel) |
168 | | - pieces.append('%s: %s' % (keyrepr, valrepr)) |
169 | | - if n > self.maxdict: |
170 | | - pieces.append(self.fillvalue) |
171 | | - s = self._join(pieces, level) |
172 | | - return '{%s}' % (s,) |
173 | | - |
174 | | - def repr_str(self, x, level): |
175 | | - s = builtins.repr(x[:self.maxstring]) |
176 | | - if len(s) > self.maxstring: |
177 | | - i = max(0, (self.maxstring-3)//2) |
178 | | - j = max(0, self.maxstring-3-i) |
179 | | - s = builtins.repr(x[:i] + x[len(x)-j:]) |
180 | | - s = s[:i] + self.fillvalue + s[len(s)-j:] |
181 | | - return s |
182 | | - |
183 | | - def repr_int(self, x, level): |
184 | | - try: |
185 | | - s = builtins.repr(x) |
186 | | - except ValueError as exc: |
187 | | - assert 'sys.set_int_max_str_digits()' in str(exc) |
188 | | - # Those imports must be deferred due to Python's build system |
189 | | - # where the reprlib module is imported before the math module. |
190 | | - import math, sys |
191 | | - # Integers with more than sys.get_int_max_str_digits() digits |
192 | | - # are rendered differently as their repr() raises a ValueError. |
193 | | - # See https://github.com/python/cpython/issues/135487. |
194 | | - k = 1 + int(math.log10(abs(x))) |
195 | | - # Note: math.log10(abs(x)) may be overestimated or underestimated, |
196 | | - # but for simplicity, we do not compute the exact number of digits. |
197 | | - max_digits = sys.get_int_max_str_digits() |
198 | | - return (f'<{x.__class__.__name__} instance with roughly {k} ' |
199 | | - f'digits (limit at {max_digits}) at 0x{id(x):x}>') |
200 | | - if len(s) > self.maxlong: |
201 | | - i = max(0, (self.maxlong-3)//2) |
202 | | - j = max(0, self.maxlong-3-i) |
203 | | - s = s[:i] + self.fillvalue + s[len(s)-j:] |
204 | | - return s |
205 | | - |
206 | | - def repr_instance(self, x, level): |
207 | | - try: |
208 | | - s = builtins.repr(x) |
209 | | - # Bugs in x.__repr__() can cause arbitrary |
210 | | - # exceptions -- then make up something |
211 | | - except Exception: |
212 | | - return '<%s instance at %#x>' % (x.__class__.__name__, id(x)) |
213 | | - if len(s) > self.maxother: |
214 | | - i = max(0, (self.maxother-3)//2) |
215 | | - j = max(0, self.maxother-3-i) |
216 | | - s = s[:i] + self.fillvalue + s[len(s)-j:] |
217 | | - return s |
218 | | - |
219 | | - |
220 | | -def _possibly_sorted(x): |
221 | | - # Since not all sequences of items can be sorted and comparison |
222 | | - # functions may raise arbitrary exceptions, return an unsorted |
223 | | - # sequence in that case. |
224 | | - try: |
225 | | - return sorted(x) |
226 | | - except Exception: |
227 | | - return list(x) |
228 | | - |
229 | | -aRepr = Repr() |
230 | | -repr = aRepr.repr |
| 9 | +# Note: Added sort_dicts parameter to Repr to allow preserving insertion order (gh-155812) |
0 commit comments