Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/numpy/core/_internal.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2A place for internal code
4Some things are more easily handled Python.
6"""
7import ast
8import re
9import sys
10import platform
12from .multiarray import dtype, array, ndarray
13try:
14 import ctypes
15except ImportError:
16 ctypes = None
18IS_PYPY = platform.python_implementation() == 'PyPy'
20if (sys.byteorder == 'little'):
21 _nbo = '<'
22else:
23 _nbo = '>'
25def _makenames_list(adict, align):
26 allfields = []
27 fnames = list(adict.keys())
28 for fname in fnames:
29 obj = adict[fname]
30 n = len(obj)
31 if not isinstance(obj, tuple) or n not in [2, 3]:
32 raise ValueError("entry not a 2- or 3- tuple")
33 if (n > 2) and (obj[2] == fname):
34 continue
35 num = int(obj[1])
36 if (num < 0):
37 raise ValueError("invalid offset.")
38 format = dtype(obj[0], align=align)
39 if (n > 2):
40 title = obj[2]
41 else:
42 title = None
43 allfields.append((fname, format, num, title))
44 # sort by offsets
45 allfields.sort(key=lambda x: x[2])
46 names = [x[0] for x in allfields]
47 formats = [x[1] for x in allfields]
48 offsets = [x[2] for x in allfields]
49 titles = [x[3] for x in allfields]
51 return names, formats, offsets, titles
53# Called in PyArray_DescrConverter function when
54# a dictionary without "names" and "formats"
55# fields is used as a data-type descriptor.
56def _usefields(adict, align):
57 try:
58 names = adict[-1]
59 except KeyError:
60 names = None
61 if names is None:
62 names, formats, offsets, titles = _makenames_list(adict, align)
63 else:
64 formats = []
65 offsets = []
66 titles = []
67 for name in names:
68 res = adict[name]
69 formats.append(res[0])
70 offsets.append(res[1])
71 if (len(res) > 2):
72 titles.append(res[2])
73 else:
74 titles.append(None)
76 return dtype({"names": names,
77 "formats": formats,
78 "offsets": offsets,
79 "titles": titles}, align)
82# construct an array_protocol descriptor list
83# from the fields attribute of a descriptor
84# This calls itself recursively but should eventually hit
85# a descriptor that has no fields and then return
86# a simple typestring
88def _array_descr(descriptor):
89 fields = descriptor.fields
90 if fields is None:
91 subdtype = descriptor.subdtype
92 if subdtype is None:
93 if descriptor.metadata is None:
94 return descriptor.str
95 else:
96 new = descriptor.metadata.copy()
97 if new:
98 return (descriptor.str, new)
99 else:
100 return descriptor.str
101 else:
102 return (_array_descr(subdtype[0]), subdtype[1])
104 names = descriptor.names
105 ordered_fields = [fields[x] + (x,) for x in names]
106 result = []
107 offset = 0
108 for field in ordered_fields:
109 if field[1] > offset:
110 num = field[1] - offset
111 result.append(('', '|V%d' % num))
112 offset += num
113 elif field[1] < offset:
114 raise ValueError(
115 "dtype.descr is not defined for types with overlapping or "
116 "out-of-order fields")
117 if len(field) > 3:
118 name = (field[2], field[3])
119 else:
120 name = field[2]
121 if field[0].subdtype:
122 tup = (name, _array_descr(field[0].subdtype[0]),
123 field[0].subdtype[1])
124 else:
125 tup = (name, _array_descr(field[0]))
126 offset += field[0].itemsize
127 result.append(tup)
129 if descriptor.itemsize > offset:
130 num = descriptor.itemsize - offset
131 result.append(('', '|V%d' % num))
133 return result
135# Build a new array from the information in a pickle.
136# Note that the name numpy.core._internal._reconstruct is embedded in
137# pickles of ndarrays made with NumPy before release 1.0
138# so don't remove the name here, or you'll
139# break backward compatibility.
140def _reconstruct(subtype, shape, dtype):
141 return ndarray.__new__(subtype, shape, dtype)
144# format_re was originally from numarray by J. Todd Miller
146format_re = re.compile(r'(?P<order1>[<>|=]?)'
147 r'(?P<repeats> *[(]?[ ,0-9]*[)]? *)'
148 r'(?P<order2>[<>|=]?)'
149 r'(?P<dtype>[A-Za-z0-9.?]*(?:\[[a-zA-Z0-9,.]+\])?)')
150sep_re = re.compile(r'\s*,\s*')
151space_re = re.compile(r'\s+$')
153# astr is a string (perhaps comma separated)
155_convorder = {'=': _nbo}
157def _commastring(astr):
158 startindex = 0
159 result = []
160 while startindex < len(astr):
161 mo = format_re.match(astr, pos=startindex)
162 try:
163 (order1, repeats, order2, dtype) = mo.groups()
164 except (TypeError, AttributeError):
165 raise ValueError('format number %d of "%s" is not recognized' %
166 (len(result)+1, astr))
167 startindex = mo.end()
168 # Separator or ending padding
169 if startindex < len(astr):
170 if space_re.match(astr, pos=startindex):
171 startindex = len(astr)
172 else:
173 mo = sep_re.match(astr, pos=startindex)
174 if not mo:
175 raise ValueError(
176 'format number %d of "%s" is not recognized' %
177 (len(result)+1, astr))
178 startindex = mo.end()
180 if order2 == '':
181 order = order1
182 elif order1 == '':
183 order = order2
184 else:
185 order1 = _convorder.get(order1, order1)
186 order2 = _convorder.get(order2, order2)
187 if (order1 != order2):
188 raise ValueError(
189 'inconsistent byte-order specification %s and %s' %
190 (order1, order2))
191 order = order1
193 if order in ['|', '=', _nbo]:
194 order = ''
195 dtype = order + dtype
196 if (repeats == ''):
197 newitem = dtype
198 else:
199 newitem = (dtype, ast.literal_eval(repeats))
200 result.append(newitem)
202 return result
204class dummy_ctype:
205 def __init__(self, cls):
206 self._cls = cls
207 def __mul__(self, other):
208 return self
209 def __call__(self, *other):
210 return self._cls(other)
211 def __eq__(self, other):
212 return self._cls == other._cls
213 def __ne__(self, other):
214 return self._cls != other._cls
216def _getintp_ctype():
217 val = _getintp_ctype.cache
218 if val is not None:
219 return val
220 if ctypes is None:
221 import numpy as np
222 val = dummy_ctype(np.intp)
223 else:
224 char = dtype('p').char
225 if (char == 'i'):
226 val = ctypes.c_int
227 elif char == 'l':
228 val = ctypes.c_long
229 elif char == 'q':
230 val = ctypes.c_longlong
231 else:
232 val = ctypes.c_long
233 _getintp_ctype.cache = val
234 return val
235_getintp_ctype.cache = None
237# Used for .ctypes attribute of ndarray
239class _missing_ctypes:
240 def cast(self, num, obj):
241 return num.value
243 class c_void_p:
244 def __init__(self, ptr):
245 self.value = ptr
248class _ctypes:
249 def __init__(self, array, ptr=None):
250 self._arr = array
252 if ctypes:
253 self._ctypes = ctypes
254 self._data = self._ctypes.c_void_p(ptr)
255 else:
256 # fake a pointer-like object that holds onto the reference
257 self._ctypes = _missing_ctypes()
258 self._data = self._ctypes.c_void_p(ptr)
259 self._data._objects = array
261 if self._arr.ndim == 0:
262 self._zerod = True
263 else:
264 self._zerod = False
266 def data_as(self, obj):
267 """
268 Return the data pointer cast to a particular c-types object.
269 For example, calling ``self._as_parameter_`` is equivalent to
270 ``self.data_as(ctypes.c_void_p)``. Perhaps you want to use the data as a
271 pointer to a ctypes array of floating-point data:
272 ``self.data_as(ctypes.POINTER(ctypes.c_double))``.
274 The returned pointer will keep a reference to the array.
275 """
276 # _ctypes.cast function causes a circular reference of self._data in
277 # self._data._objects. Attributes of self._data cannot be released
278 # until gc.collect is called. Make a copy of the pointer first then let
279 # it hold the array reference. This is a workaround to circumvent the
280 # CPython bug https://bugs.python.org/issue12836
281 ptr = self._ctypes.cast(self._data, obj)
282 ptr._arr = self._arr
283 return ptr
285 def shape_as(self, obj):
286 """
287 Return the shape tuple as an array of some other c-types
288 type. For example: ``self.shape_as(ctypes.c_short)``.
289 """
290 if self._zerod:
291 return None
292 return (obj*self._arr.ndim)(*self._arr.shape)
294 def strides_as(self, obj):
295 """
296 Return the strides tuple as an array of some other
297 c-types type. For example: ``self.strides_as(ctypes.c_longlong)``.
298 """
299 if self._zerod:
300 return None
301 return (obj*self._arr.ndim)(*self._arr.strides)
303 @property
304 def data(self):
305 """
306 A pointer to the memory area of the array as a Python integer.
307 This memory area may contain data that is not aligned, or not in correct
308 byte-order. The memory area may not even be writeable. The array
309 flags and data-type of this array should be respected when passing this
310 attribute to arbitrary C-code to avoid trouble that can include Python
311 crashing. User Beware! The value of this attribute is exactly the same
312 as ``self._array_interface_['data'][0]``.
314 Note that unlike ``data_as``, a reference will not be kept to the array:
315 code like ``ctypes.c_void_p((a + b).ctypes.data)`` will result in a
316 pointer to a deallocated array, and should be spelt
317 ``(a + b).ctypes.data_as(ctypes.c_void_p)``
318 """
319 return self._data.value
321 @property
322 def shape(self):
323 """
324 (c_intp*self.ndim): A ctypes array of length self.ndim where
325 the basetype is the C-integer corresponding to ``dtype('p')`` on this
326 platform. This base-type could be `ctypes.c_int`, `ctypes.c_long`, or
327 `ctypes.c_longlong` depending on the platform.
328 The c_intp type is defined accordingly in `numpy.ctypeslib`.
329 The ctypes array contains the shape of the underlying array.
330 """
331 return self.shape_as(_getintp_ctype())
333 @property
334 def strides(self):
335 """
336 (c_intp*self.ndim): A ctypes array of length self.ndim where
337 the basetype is the same as for the shape attribute. This ctypes array
338 contains the strides information from the underlying array. This strides
339 information is important for showing how many bytes must be jumped to
340 get to the next element in the array.
341 """
342 return self.strides_as(_getintp_ctype())
344 @property
345 def _as_parameter_(self):
346 """
347 Overrides the ctypes semi-magic method
349 Enables `c_func(some_array.ctypes)`
350 """
351 return self.data_as(ctypes.c_void_p)
353 # kept for compatibility
354 get_data = data.fget
355 get_shape = shape.fget
356 get_strides = strides.fget
357 get_as_parameter = _as_parameter_.fget
360def _newnames(datatype, order):
361 """
362 Given a datatype and an order object, return a new names tuple, with the
363 order indicated
364 """
365 oldnames = datatype.names
366 nameslist = list(oldnames)
367 if isinstance(order, str):
368 order = [order]
369 seen = set()
370 if isinstance(order, (list, tuple)):
371 for name in order:
372 try:
373 nameslist.remove(name)
374 except ValueError:
375 if name in seen:
376 raise ValueError("duplicate field name: %s" % (name,))
377 else:
378 raise ValueError("unknown field name: %s" % (name,))
379 seen.add(name)
380 return tuple(list(order) + nameslist)
381 raise ValueError("unsupported order value: %s" % (order,))
383def _copy_fields(ary):
384 """Return copy of structured array with padding between fields removed.
386 Parameters
387 ----------
388 ary : ndarray
389 Structured array from which to remove padding bytes
391 Returns
392 -------
393 ary_copy : ndarray
394 Copy of ary with padding bytes removed
395 """
396 dt = ary.dtype
397 copy_dtype = {'names': dt.names,
398 'formats': [dt.fields[name][0] for name in dt.names]}
399 return array(ary, dtype=copy_dtype, copy=True)
401def _getfield_is_safe(oldtype, newtype, offset):
402 """ Checks safety of getfield for object arrays.
404 As in _view_is_safe, we need to check that memory containing objects is not
405 reinterpreted as a non-object datatype and vice versa.
407 Parameters
408 ----------
409 oldtype : data-type
410 Data type of the original ndarray.
411 newtype : data-type
412 Data type of the field being accessed by ndarray.getfield
413 offset : int
414 Offset of the field being accessed by ndarray.getfield
416 Raises
417 ------
418 TypeError
419 If the field access is invalid
421 """
422 if newtype.hasobject or oldtype.hasobject:
423 if offset == 0 and newtype == oldtype:
424 return
425 if oldtype.names is not None:
426 for name in oldtype.names:
427 if (oldtype.fields[name][1] == offset and
428 oldtype.fields[name][0] == newtype):
429 return
430 raise TypeError("Cannot get/set field of an object array")
431 return
433def _view_is_safe(oldtype, newtype):
434 """ Checks safety of a view involving object arrays, for example when
435 doing::
437 np.zeros(10, dtype=oldtype).view(newtype)
439 Parameters
440 ----------
441 oldtype : data-type
442 Data type of original ndarray
443 newtype : data-type
444 Data type of the view
446 Raises
447 ------
448 TypeError
449 If the new type is incompatible with the old type.
451 """
453 # if the types are equivalent, there is no problem.
454 # for example: dtype((np.record, 'i4,i4')) == dtype((np.void, 'i4,i4'))
455 if oldtype == newtype:
456 return
458 if newtype.hasobject or oldtype.hasobject:
459 raise TypeError("Cannot change data-type for object array.")
460 return
462# Given a string containing a PEP 3118 format specifier,
463# construct a NumPy dtype
465_pep3118_native_map = {
466 '?': '?',
467 'c': 'S1',
468 'b': 'b',
469 'B': 'B',
470 'h': 'h',
471 'H': 'H',
472 'i': 'i',
473 'I': 'I',
474 'l': 'l',
475 'L': 'L',
476 'q': 'q',
477 'Q': 'Q',
478 'e': 'e',
479 'f': 'f',
480 'd': 'd',
481 'g': 'g',
482 'Zf': 'F',
483 'Zd': 'D',
484 'Zg': 'G',
485 's': 'S',
486 'w': 'U',
487 'O': 'O',
488 'x': 'V', # padding
489}
490_pep3118_native_typechars = ''.join(_pep3118_native_map.keys())
492_pep3118_standard_map = {
493 '?': '?',
494 'c': 'S1',
495 'b': 'b',
496 'B': 'B',
497 'h': 'i2',
498 'H': 'u2',
499 'i': 'i4',
500 'I': 'u4',
501 'l': 'i4',
502 'L': 'u4',
503 'q': 'i8',
504 'Q': 'u8',
505 'e': 'f2',
506 'f': 'f',
507 'd': 'd',
508 'Zf': 'F',
509 'Zd': 'D',
510 's': 'S',
511 'w': 'U',
512 'O': 'O',
513 'x': 'V', # padding
514}
515_pep3118_standard_typechars = ''.join(_pep3118_standard_map.keys())
517_pep3118_unsupported_map = {
518 'u': 'UCS-2 strings',
519 '&': 'pointers',
520 't': 'bitfields',
521 'X': 'function pointers',
522}
524class _Stream:
525 def __init__(self, s):
526 self.s = s
527 self.byteorder = '@'
529 def advance(self, n):
530 res = self.s[:n]
531 self.s = self.s[n:]
532 return res
534 def consume(self, c):
535 if self.s[:len(c)] == c:
536 self.advance(len(c))
537 return True
538 return False
540 def consume_until(self, c):
541 if callable(c):
542 i = 0
543 while i < len(self.s) and not c(self.s[i]):
544 i = i + 1
545 return self.advance(i)
546 else:
547 i = self.s.index(c)
548 res = self.advance(i)
549 self.advance(len(c))
550 return res
552 @property
553 def next(self):
554 return self.s[0]
556 def __bool__(self):
557 return bool(self.s)
560def _dtype_from_pep3118(spec):
561 stream = _Stream(spec)
562 dtype, align = __dtype_from_pep3118(stream, is_subdtype=False)
563 return dtype
565def __dtype_from_pep3118(stream, is_subdtype):
566 field_spec = dict(
567 names=[],
568 formats=[],
569 offsets=[],
570 itemsize=0
571 )
572 offset = 0
573 common_alignment = 1
574 is_padding = False
576 # Parse spec
577 while stream:
578 value = None
580 # End of structure, bail out to upper level
581 if stream.consume('}'):
582 break
584 # Sub-arrays (1)
585 shape = None
586 if stream.consume('('):
587 shape = stream.consume_until(')')
588 shape = tuple(map(int, shape.split(',')))
590 # Byte order
591 if stream.next in ('@', '=', '<', '>', '^', '!'):
592 byteorder = stream.advance(1)
593 if byteorder == '!':
594 byteorder = '>'
595 stream.byteorder = byteorder
597 # Byte order characters also control native vs. standard type sizes
598 if stream.byteorder in ('@', '^'):
599 type_map = _pep3118_native_map
600 type_map_chars = _pep3118_native_typechars
601 else:
602 type_map = _pep3118_standard_map
603 type_map_chars = _pep3118_standard_typechars
605 # Item sizes
606 itemsize_str = stream.consume_until(lambda c: not c.isdigit())
607 if itemsize_str:
608 itemsize = int(itemsize_str)
609 else:
610 itemsize = 1
612 # Data types
613 is_padding = False
615 if stream.consume('T{'):
616 value, align = __dtype_from_pep3118(
617 stream, is_subdtype=True)
618 elif stream.next in type_map_chars:
619 if stream.next == 'Z':
620 typechar = stream.advance(2)
621 else:
622 typechar = stream.advance(1)
624 is_padding = (typechar == 'x')
625 dtypechar = type_map[typechar]
626 if dtypechar in 'USV':
627 dtypechar += '%d' % itemsize
628 itemsize = 1
629 numpy_byteorder = {'@': '=', '^': '='}.get(
630 stream.byteorder, stream.byteorder)
631 value = dtype(numpy_byteorder + dtypechar)
632 align = value.alignment
633 elif stream.next in _pep3118_unsupported_map:
634 desc = _pep3118_unsupported_map[stream.next]
635 raise NotImplementedError(
636 "Unrepresentable PEP 3118 data type {!r} ({})"
637 .format(stream.next, desc))
638 else:
639 raise ValueError("Unknown PEP 3118 data type specifier %r" % stream.s)
641 #
642 # Native alignment may require padding
643 #
644 # Here we assume that the presence of a '@' character implicitly implies
645 # that the start of the array is *already* aligned.
646 #
647 extra_offset = 0
648 if stream.byteorder == '@':
649 start_padding = (-offset) % align
650 intra_padding = (-value.itemsize) % align
652 offset += start_padding
654 if intra_padding != 0:
655 if itemsize > 1 or (shape is not None and _prod(shape) > 1):
656 # Inject internal padding to the end of the sub-item
657 value = _add_trailing_padding(value, intra_padding)
658 else:
659 # We can postpone the injection of internal padding,
660 # as the item appears at most once
661 extra_offset += intra_padding
663 # Update common alignment
664 common_alignment = _lcm(align, common_alignment)
666 # Convert itemsize to sub-array
667 if itemsize != 1:
668 value = dtype((value, (itemsize,)))
670 # Sub-arrays (2)
671 if shape is not None:
672 value = dtype((value, shape))
674 # Field name
675 if stream.consume(':'):
676 name = stream.consume_until(':')
677 else:
678 name = None
680 if not (is_padding and name is None):
681 if name is not None and name in field_spec['names']:
682 raise RuntimeError("Duplicate field name '%s' in PEP3118 format"
683 % name)
684 field_spec['names'].append(name)
685 field_spec['formats'].append(value)
686 field_spec['offsets'].append(offset)
688 offset += value.itemsize
689 offset += extra_offset
691 field_spec['itemsize'] = offset
693 # extra final padding for aligned types
694 if stream.byteorder == '@':
695 field_spec['itemsize'] += (-offset) % common_alignment
697 # Check if this was a simple 1-item type, and unwrap it
698 if (field_spec['names'] == [None]
699 and field_spec['offsets'][0] == 0
700 and field_spec['itemsize'] == field_spec['formats'][0].itemsize
701 and not is_subdtype):
702 ret = field_spec['formats'][0]
703 else:
704 _fix_names(field_spec)
705 ret = dtype(field_spec)
707 # Finished
708 return ret, common_alignment
710def _fix_names(field_spec):
711 """ Replace names which are None with the next unused f%d name """
712 names = field_spec['names']
713 for i, name in enumerate(names):
714 if name is not None:
715 continue
717 j = 0
718 while True:
719 name = 'f{}'.format(j)
720 if name not in names:
721 break
722 j = j + 1
723 names[i] = name
725def _add_trailing_padding(value, padding):
726 """Inject the specified number of padding bytes at the end of a dtype"""
727 if value.fields is None:
728 field_spec = dict(
729 names=['f0'],
730 formats=[value],
731 offsets=[0],
732 itemsize=value.itemsize
733 )
734 else:
735 fields = value.fields
736 names = value.names
737 field_spec = dict(
738 names=names,
739 formats=[fields[name][0] for name in names],
740 offsets=[fields[name][1] for name in names],
741 itemsize=value.itemsize
742 )
744 field_spec['itemsize'] += padding
745 return dtype(field_spec)
747def _prod(a):
748 p = 1
749 for x in a:
750 p *= x
751 return p
753def _gcd(a, b):
754 """Calculate the greatest common divisor of a and b"""
755 while b:
756 a, b = b, a % b
757 return a
759def _lcm(a, b):
760 return a // _gcd(a, b) * b
762def array_ufunc_errmsg_formatter(dummy, ufunc, method, *inputs, **kwargs):
763 """ Format the error message for when __array_ufunc__ gives up. """
764 args_string = ', '.join(['{!r}'.format(arg) for arg in inputs] +
765 ['{}={!r}'.format(k, v)
766 for k, v in kwargs.items()])
767 args = inputs + kwargs.get('out', ())
768 types_string = ', '.join(repr(type(arg).__name__) for arg in args)
769 return ('operand type(s) all returned NotImplemented from '
770 '__array_ufunc__({!r}, {!r}, {}): {}'
771 .format(ufunc, method, args_string, types_string))
774def array_function_errmsg_formatter(public_api, types):
775 """ Format the error message for when __array_ufunc__ gives up. """
776 func_name = '{}.{}'.format(public_api.__module__, public_api.__name__)
777 return ("no implementation found for '{}' on types that implement "
778 '__array_function__: {}'.format(func_name, list(types)))
781def _ufunc_doc_signature_formatter(ufunc):
782 """
783 Builds a signature string which resembles PEP 457
785 This is used to construct the first line of the docstring
786 """
788 # input arguments are simple
789 if ufunc.nin == 1:
790 in_args = 'x'
791 else:
792 in_args = ', '.join('x{}'.format(i+1) for i in range(ufunc.nin))
794 # output arguments are both keyword or positional
795 if ufunc.nout == 0:
796 out_args = ', /, out=()'
797 elif ufunc.nout == 1:
798 out_args = ', /, out=None'
799 else:
800 out_args = '[, {positional}], / [, out={default}]'.format(
801 positional=', '.join(
802 'out{}'.format(i+1) for i in range(ufunc.nout)),
803 default=repr((None,)*ufunc.nout)
804 )
806 # keyword only args depend on whether this is a gufunc
807 kwargs = (
808 ", casting='same_kind'"
809 ", order='K'"
810 ", dtype=None"
811 ", subok=True"
812 "[, signature"
813 ", extobj]"
814 )
815 if ufunc.signature is None:
816 kwargs = ", where=True" + kwargs
818 # join all the parts together
819 return '{name}({in_args}{out_args}, *{kwargs})'.format(
820 name=ufunc.__name__,
821 in_args=in_args,
822 out_args=out_args,
823 kwargs=kwargs
824 )
827def npy_ctypes_check(cls):
828 # determine if a class comes from ctypes, in order to work around
829 # a bug in the buffer protocol for those objects, bpo-10746
830 try:
831 # ctypes class are new-style, so have an __mro__. This probably fails
832 # for ctypes classes with multiple inheritance.
833 if IS_PYPY:
834 # (..., _ctypes.basics._CData, Bufferable, object)
835 ctype_base = cls.__mro__[-3]
836 else:
837 # # (..., _ctypes._CData, object)
838 ctype_base = cls.__mro__[-2]
839 # right now, they're part of the _ctypes module
840 return '_ctypes' in ctype_base.__module__
841 except Exception:
842 return False
845class recursive:
846 '''
847 A decorator class for recursive nested functions.
848 Naive recursive nested functions hold a reference to themselves:
850 def outer(*args):
851 def stringify_leaky(arg0, *arg1):
852 if len(arg1) > 0:
853 return stringify_leaky(*arg1) # <- HERE
854 return str(arg0)
855 stringify_leaky(*args)
857 This design pattern creates a reference cycle that is difficult for a
858 garbage collector to resolve. The decorator class prevents the
859 cycle by passing the nested function in as an argument `self`:
861 def outer(*args):
862 @recursive
863 def stringify(self, arg0, *arg1):
864 if len(arg1) > 0:
865 return self(*arg1)
866 return str(arg0)
867 stringify(*args)
869 '''
870 def __init__(self, func):
871 self.func = func
872 def __call__(self, *args, **kwargs):
873 return self.func(self, *args, **kwargs)