summaryrefslogtreecommitdiff
path: root/numpy/core
diff options
context:
space:
mode:
authorEric Wieser <wieser.eric@gmail.com>2017-04-20 12:06:45 +0100
committerEric Wieser <wieser.eric@gmail.com>2017-04-25 10:28:30 +0100
commit1e65a2af062012ab4fd4f9954b45a608a915e56d (patch)
treedd31b17bc5d72c6a879cefea9d96bf6ffa10a597 /numpy/core
parenta2aea7757aacaa140f22910de6b81f9196a4aecc (diff)
downloadnumpy-1e65a2af062012ab4fd4f9954b45a608a915e56d.tar.gz
BUG: Prevent infinite recursion when printing self-containing arrays
Fixes #8960
Diffstat (limited to 'numpy/core')
-rw-r--r--numpy/core/arrayprint.py48
-rw-r--r--numpy/core/tests/test_arrayprint.py18
2 files changed, 64 insertions, 2 deletions
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
index 4e62a42fc..dba9dffb3 100644
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -16,7 +16,18 @@ __docformat__ = 'restructuredtext'
# and by Travis Oliphant 2005-8-22 for numpy
import sys
-from functools import reduce
+import functools
+if sys.version_info[0] >= 3:
+ try:
+ from _thread import get_ident
+ except ImportError:
+ from _dummy_thread import get_ident
+else:
+ try:
+ from thread import get_ident
+ except ImportError:
+ from dummy_thread import get_ident
+
from . import numerictypes as _nt
from .umath import maximum, minimum, absolute, not_equal, isnan, isinf
from .multiarray import (array, format_longfloat, datetime_as_string,
@@ -342,6 +353,39 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ',
_summaryEdgeItems, summary_insert)[:-1]
return lst
+
+def _recursive_guard(fillvalue='...'):
+ """
+ Like the python 3.2 reprlib.recursive_repr, but forwards *args and **kwargs
+
+ Decorates a function such that if it calls itself with the same first
+ argument, it returns `fillvalue` instead of recursing.
+
+ Largely copied from reprlib.recursive_repr
+ """
+
+ def decorating_function(f):
+ repr_running = set()
+
+ @functools.wraps(f)
+ def wrapper(self, *args, **kwargs):
+ key = id(self), get_ident()
+ if key in repr_running:
+ return fillvalue
+ repr_running.add(key)
+ try:
+ return f(self, *args, **kwargs)
+ finally:
+ repr_running.discard(key)
+
+ return wrapper
+
+ return decorating_function
+
+
+# gracefully handle recursive calls - this comes up when object arrays contain
+# themselves
+@_recursive_guard()
def array2string(a, max_line_width=None, precision=None,
suppress_small=None, separator=' ', prefix="",
style=repr, formatter=None):
@@ -460,7 +504,7 @@ def array2string(a, max_line_width=None, precision=None,
lst = format_function(arr[0])
else:
lst = style(x)
- elif reduce(product, a.shape) == 0:
+ elif functools.reduce(product, a.shape) == 0:
# treat as a null array if any of shape elements == 0
lst = "[]"
else:
diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py
index 62effc425..b228527da 100644
--- a/numpy/core/tests/test_arrayprint.py
+++ b/numpy/core/tests/test_arrayprint.py
@@ -34,6 +34,24 @@ class TestArrayRepr(object):
" dtype=[('a', '<i4')])"
)
+ def test_self_containing(self):
+ arr0d = np.array(None)
+ arr0d[()] = arr0d
+ assert_equal(repr(arr0d),
+ 'array(array(..., dtype=object), dtype=object)')
+
+ arr1d = np.array([None, None])
+ arr1d[1] = arr1d
+ assert_equal(repr(arr1d),
+ 'array([None, array(..., dtype=object)], dtype=object)')
+
+ first = np.array(None)
+ second = np.array(None)
+ first[()] = second
+ second[()] = first
+ assert_equal(repr(first),
+ 'array(array(array(..., dtype=object), dtype=object), dtype=object)')
+
class TestComplexArray(TestCase):
def test_str(self):