summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Haldane <allan.haldane@gmail.com>2018-02-17 14:36:49 -0500
committerAllan Haldane <allan.haldane@gmail.com>2018-02-18 23:36:25 -0500
commita6963342e063cc0cf2200ee483ff5daef77f0e8a (patch)
tree51581ada41ba63162f20f729d85e573bedba4235
parenta82363a39f4313bf9626374d717b220dcc7da6f9 (diff)
downloadnumpy-a6963342e063cc0cf2200ee483ff5daef77f0e8a.tar.gz
BUG: break cyclic refs in recursive closures
Fixes #10620
-rw-r--r--numpy/core/arrayprint.py19
-rw-r--r--numpy/core/shape_base.py8
-rw-r--r--numpy/core/tests/test_arrayprint.py14
-rw-r--r--numpy/lib/npyio.py5
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)