summaryrefslogtreecommitdiff
path: root/numpy/lib/tests/test_function_base.py
diff options
context:
space:
mode:
authorMatti Picus <matti.picus@gmail.com>2019-01-09 16:28:09 +0200
committerSebastian Berg <sebastian@sipsolutions.net>2019-01-09 15:28:09 +0100
commitf5b6850f231de80b067e06e01d11bec4bd535c58 (patch)
tree381b919bb15da5f5bef8b4d6075fe91ab96cd709 /numpy/lib/tests/test_function_base.py
parentad0e902717d1245d17856d47d7f16bc7817da866 (diff)
downloadnumpy-f5b6850f231de80b067e06e01d11bec4bd535c58.tar.gz
BUG: reference cycle in np.vectorize (#11977)
This implements cyclic support by adding `tp_traverse` to ufuns which may contain a user provided object function (`np.frompyfunc`). Ufuncs that do not add this are not added to the circular reference count tracking. The ufunc does not need to implement `tp_clear` because it is an immutable object.
Diffstat (limited to 'numpy/lib/tests/test_function_base.py')
-rw-r--r--numpy/lib/tests/test_function_base.py49
1 files changed, 47 insertions, 2 deletions
diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py
index 0976be114..d9a97db1b 100644
--- a/numpy/lib/tests/test_function_base.py
+++ b/numpy/lib/tests/test_function_base.py
@@ -4,6 +4,7 @@ import operator
import warnings
import sys
import decimal
+import types
import pytest
import numpy as np
@@ -24,6 +25,7 @@ from numpy.lib import (
from numpy.compat import long
+PY2 = sys.version_info[0] == 2
def get_mat(n):
data = np.arange(n)
@@ -353,9 +355,9 @@ class TestAverage(object):
assert_equal(type(np.average(a, weights=w)), subclass)
def test_upcasting(self):
- types = [('i4', 'i4', 'f8'), ('i4', 'f4', 'f8'), ('f4', 'i4', 'f8'),
+ typs = [('i4', 'i4', 'f8'), ('i4', 'f4', 'f8'), ('f4', 'i4', 'f8'),
('f4', 'f4', 'f4'), ('f4', 'f8', 'f8')]
- for at, wt, rt in types:
+ for at, wt, rt in typs:
a = np.array([[1,2],[3,4]], dtype=at)
w = np.array([[1,2],[3,4]], dtype=wt)
assert_equal(np.average(a, weights=w).dtype, np.dtype(rt))
@@ -1498,6 +1500,49 @@ class TestVectorize(object):
f(x)
+class TestLeaks(object):
+ class A(object):
+ iters = 20
+
+ def bound(self, *args):
+ return 0
+
+ @staticmethod
+ def unbound(*args):
+ return 0
+
+ @pytest.mark.skipif(not HAS_REFCOUNT, reason="Python lacks refcounts")
+ @pytest.mark.parametrize('name, incr', [
+ ('bound', A.iters),
+ ('unbound', 0),
+ ])
+ def test_frompyfunc_leaks(self, name, incr):
+ # exposed in gh-11867 as np.vectorized, but the problem stems from
+ # frompyfunc.
+ # class.attribute = np.frompyfunc(<method>) creates a
+ # reference cycle if <method> is a bound class method. It requires a
+ # gc collection cycle to break the cycle (on CPython 3)
+ import gc
+ A_func = getattr(self.A, name)
+ gc.disable()
+ try:
+ refcount = sys.getrefcount(A_func)
+ for i in range(self.A.iters):
+ a = self.A()
+ a.f = np.frompyfunc(getattr(a, name), 1, 1)
+ out = a.f(np.arange(10))
+ a = None
+ if PY2:
+ assert_equal(sys.getrefcount(A_func), refcount)
+ else:
+ # A.func is part of a reference cycle if incr is non-zero
+ assert_equal(sys.getrefcount(A_func), refcount + incr)
+ for i in range(5):
+ gc.collect()
+ assert_equal(sys.getrefcount(A_func), refcount)
+ finally:
+ gc.enable()
+
class TestDigitize(object):
def test_forward(self):