diff options
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/_internal.py | 55 | ||||
-rw-r--r-- | numpy/core/src/umath/override.c | 18 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 32 |
3 files changed, 40 insertions, 65 deletions
diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index b0ea603e1..5fd643505 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -247,55 +247,13 @@ class _missing_ctypes(object): self.value = ptr -class _unsafe_first_element_pointer(object): - """ - Helper to allow viewing an array as a ctypes pointer to the first element - - This avoids: - * dealing with strides - * `.view` rejecting object-containing arrays - * `memoryview` not supporting overlapping fields - """ - def __init__(self, arr): - self.base = arr - - @property - def __array_interface__(self): - i = dict( - shape=(), - typestr='|V0', - data=(self.base.__array_interface__['data'][0], False), - strides=(), - version=3, - ) - return i - - -def _get_void_ptr(arr): - """ - Get a `ctypes.c_void_p` to arr.data, that keeps a reference to the array - """ - import numpy as np - # convert to a 0d array that has a data pointer referrign to the start - # of arr. This holds a reference to arr. - simple_arr = np.asarray(_unsafe_first_element_pointer(arr)) - - # create a `char[0]` using the same memory. - c_arr = (ctypes.c_char * 0).from_buffer(simple_arr) - - # finally cast to void* - return ctypes.cast(ctypes.pointer(c_arr), ctypes.c_void_p) - - class _ctypes(object): def __init__(self, array, ptr=None): self._arr = array if ctypes: self._ctypes = ctypes - # get a void pointer to the buffer, which keeps the array alive - self._data = _get_void_ptr(array) - assert self._data.value == ptr + self._data = self._ctypes.c_void_p(ptr) else: # fake a pointer-like object that holds onto the reference self._ctypes = _missing_ctypes() @@ -317,7 +275,14 @@ class _ctypes(object): The returned pointer will keep a reference to the array. """ - return self._ctypes.cast(self._data, obj) + # _ctypes.cast function causes a circular reference of self._data in + # self._data._objects. Attributes of self._data cannot be released + # until gc.collect is called. Make a copy of the pointer first then let + # it hold the array reference. This is a workaround to circumvent the + # CPython bug https://bugs.python.org/issue12836 + ptr = self._ctypes.cast(self._data, obj) + ptr._arr = self._arr + return ptr def shape_as(self, obj): """ @@ -385,7 +350,7 @@ class _ctypes(object): Enables `c_func(some_array.ctypes)` """ - return self._data + return self.data_as(ctypes.c_void_p) # kept for compatibility get_data = data.fget diff --git a/numpy/core/src/umath/override.c b/numpy/core/src/umath/override.c index 8d67f96ac..43bed425c 100644 --- a/numpy/core/src/umath/override.c +++ b/numpy/core/src/umath/override.c @@ -494,32 +494,18 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method, } else { /* not a tuple */ - if (nout > 1 && DEPRECATE("passing a single argument to the " - "'out' keyword argument of a " - "ufunc with\n" - "more than one output will " - "result in an error in the " - "future") < 0) { - /* - * If the deprecation is removed, also remove the loop - * below setting tuple items to None (but keep this future - * error message.) - */ + if (nout > 1) { PyErr_SetString(PyExc_TypeError, "'out' must be a tuple of arguments"); goto fail; } if (out != Py_None) { /* not already a tuple and not None */ - PyObject *out_tuple = PyTuple_New(nout); + PyObject *out_tuple = PyTuple_New(1); if (out_tuple == NULL) { goto fail; } - for (i = 1; i < nout; i++) { - Py_INCREF(Py_None); - PyTuple_SET_ITEM(out_tuple, i, Py_None); - } /* out was borrowed ref; make it permanent */ Py_INCREF(out); /* steals reference */ diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 9b124f603..66e3e3c60 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3602,10 +3602,10 @@ class TestBinop(object): assert_equal(np.modf(dummy, out=(None, a)), (1,)) assert_equal(np.modf(dummy, out=(dummy, a)), (1,)) assert_equal(np.modf(a, out=(dummy, a)), 0) - with warnings.catch_warnings(record=True) as w: - warnings.filterwarnings('always', '', DeprecationWarning) - assert_equal(np.modf(dummy, out=a), (0,)) - assert_(w[0].category is DeprecationWarning) + with assert_raises(TypeError): + # Out argument must be tuple, since there are multiple outputs + np.modf(dummy, out=a) + assert_raises(ValueError, np.modf, dummy, out=(a,)) # 2 inputs, 1 output @@ -7975,6 +7975,8 @@ class TestFormat(object): dst = object.__format__(a, '30') assert_equal(res, dst) +from numpy.testing import IS_PYPY + class TestCTypes(object): def test_ctypes_is_available(self): @@ -8041,7 +8043,29 @@ class TestCTypes(object): # but when the `ctypes_ptr` object dies, so should `arr` del ctypes_ptr + if IS_PYPY: + # Pypy does not recycle arr objects immediately. Trigger gc to + # release arr. Cpython uses refcounts. An explicit call to gc + # should not be needed here. + break_cycles() + assert_(arr_ref() is None, "unknowable whether ctypes pointer holds a reference") + + def test_ctypes_as_parameter_holds_reference(self): + arr = np.array([None]).copy() + + arr_ref = weakref.ref(arr) + + ctypes_ptr = arr.ctypes._as_parameter_ + + # `ctypes_ptr` should hold onto `arr` + del arr break_cycles() + assert_(arr_ref() is not None, "ctypes pointer did not hold onto a reference") + + # but when the `ctypes_ptr` object dies, so should `arr` + del ctypes_ptr + if IS_PYPY: + break_cycles() assert_(arr_ref() is None, "unknowable whether ctypes pointer holds a reference") |