diff options
author | Allan Haldane <allan.haldane@gmail.com> | 2018-02-17 14:36:49 -0500 |
---|---|---|
committer | Allan Haldane <allan.haldane@gmail.com> | 2018-02-18 23:36:25 -0500 |
commit | a6963342e063cc0cf2200ee483ff5daef77f0e8a (patch) | |
tree | 51581ada41ba63162f20f729d85e573bedba4235 | |
parent | a82363a39f4313bf9626374d717b220dcc7da6f9 (diff) | |
download | numpy-a6963342e063cc0cf2200ee483ff5daef77f0e8a.tar.gz |
BUG: break cyclic refs in recursive closures
Fixes #10620
-rw-r--r-- | numpy/core/arrayprint.py | 19 | ||||
-rw-r--r-- | numpy/core/shape_base.py | 8 | ||||
-rw-r--r-- | numpy/core/tests/test_arrayprint.py | 14 | ||||
-rw-r--r-- | numpy/lib/npyio.py | 5 |
4 files changed, 37 insertions, 9 deletions
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 284c57867..81a34aba9 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -739,7 +739,8 @@ def _formatArray(a, format_function, line_width, next_line_prefix, s += hanging_indent + summary_insert + line_sep for i in range(trailing_items, 1, -1): - nested = recurser(index + (-i,), next_hanging_indent, next_width) + nested = recurser(index + (-i,), next_hanging_indent, + next_width) s += hanging_indent + nested + line_sep nested = recurser(index + (-1,), next_hanging_indent, next_width) @@ -749,12 +750,16 @@ def _formatArray(a, format_function, line_width, next_line_prefix, s = '[' + s[len(hanging_indent):] + ']' return s - # invoke the recursive part with an initial index and prefix - return recurser( - index=(), - hanging_indent=next_line_prefix, - curr_width=line_width) - + try: + # invoke the recursive part with an initial index and prefix + return recurser(index=(), + hanging_indent=next_line_prefix, + curr_width=line_width) + finally: + # recursive closures have a cyclic reference to themselves, which + # requires gc to collect (gh-10620). To avoid this problem, for + # performance and PyPy friendliness, we break the cycle: + recurser = None def _none_or_positive_arg(x, name): if x is None: diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py index 65c3ed00d..319c25088 100644 --- a/numpy/core/shape_base.py +++ b/numpy/core/shape_base.py @@ -446,7 +446,13 @@ def _block(arrays, max_depth, result_ndim): # type(arrays) is not list return atleast_nd(arrays, result_ndim) - return block_recursion(arrays) + try: + return block_recursion(arrays) + finally: + # recursive closures have a cyclic reference to themselves, which + # requires gc to collect (gh-10620). To avoid this problem, for + # performance and PyPy friendliness, we break the cycle: + block_recursion = None def block(arrays): diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index f70b6a333..7e76d442d 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function -import sys +import sys, gc import numpy as np from numpy.testing import ( @@ -355,6 +355,18 @@ class TestArray2String(object): "[ 'xxxxx']" ) + def test_refcount(self): + # make sure we do not hold references to the array due to a recursive + # closure (gh-10620) + gc.disable() + a = np.arange(2) + r1 = sys.getrefcount(a) + np.array2string(a) + np.array2string(a) + r2 = sys.getrefcount(a) + gc.collect() + gc.enable() + assert_(r1 == r2) class TestPrintOptions(object): """Test getting and setting global print options.""" diff --git a/numpy/lib/npyio.py b/numpy/lib/npyio.py index 9e979bbe6..76b135cc2 100644 --- a/numpy/lib/npyio.py +++ b/numpy/lib/npyio.py @@ -1101,6 +1101,11 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, finally: if fown: fh.close() + # recursive closures have a cyclic reference to themselves, which + # requires gc to collect (gh-10620). To avoid this problem, for + # performance and PyPy friendliness, we break the cycle: + flatten_dtype_internal = None + pack_items = None if X is None: X = np.array([], dtype) |