• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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