1"""Redo the builtin repr() (representation) but with limits on most sizes.""" 2 3__all__ = ["Repr", "repr", "recursive_repr"] 4 5import builtins 6from itertools import islice 7from _thread import get_ident 8 9def 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.__annotations__ = getattr(user_function, '__annotations__', {}) 32 wrapper.__type_params__ = getattr(user_function, '__type_params__', ()) 33 wrapper.__wrapped__ = user_function 34 return wrapper 35 36 return decorating_function 37 38class 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 s = builtins.repr(x) # XXX Hope this isn't too slow... 185 if len(s) > self.maxlong: 186 i = max(0, (self.maxlong-3)//2) 187 j = max(0, self.maxlong-3-i) 188 s = s[:i] + self.fillvalue + s[len(s)-j:] 189 return s 190 191 def repr_instance(self, x, level): 192 try: 193 s = builtins.repr(x) 194 # Bugs in x.__repr__() can cause arbitrary 195 # exceptions -- then make up something 196 except Exception: 197 return '<%s instance at %#x>' % (x.__class__.__name__, id(x)) 198 if len(s) > self.maxother: 199 i = max(0, (self.maxother-3)//2) 200 j = max(0, self.maxother-3-i) 201 s = s[:i] + self.fillvalue + s[len(s)-j:] 202 return s 203 204 205def _possibly_sorted(x): 206 # Since not all sequences of items can be sorted and comparison 207 # functions may raise arbitrary exceptions, return an unsorted 208 # sequence in that case. 209 try: 210 return sorted(x) 211 except Exception: 212 return list(x) 213 214aRepr = Repr() 215repr = aRepr.repr 216