8000 Merge pull request #10974 from numpy/backport_10824 · r-devulap/numpy@1f7fa87 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1f7fa87

Browse files
authored
Merge pull request numpy#10974 from numpy/backport_10824
(Backport 1.14.3) BUG: test, fix PyArray_DiscardWritebackIfCopy refcount issue and docu…
2 parents 4b40612 + cc91ca2 commit 1f7fa87

File tree

4 files changed

+70
-20
lines changed

4 files changed

+70
-20
lines changed

doc/source/reference/c-api.array.rst

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,7 +1359,7 @@ Special functions for NPY_OBJECT
13591359
.. c:function:: int PyArray_SetWritebackIfCopyBase(PyArrayObject* arr, PyArrayObject* base)
13601360
13611361
Precondition: ``arr`` is a copy of ``base`` (though possibly with different
1362-
strides, ordering, etc.) Sets the :c:data:`NPY_ARRAY_WRITEBACKIFCOPY` flag
1362+
strides, ordering, etc.) Sets the :c:data:`NPY_ARRAY_WRITEBACKIFCOPY` flag
13631363
and ``arr->base``, and set ``base`` to READONLY. Call
13641364
:c:func:`PyArray_ResolveWritebackIfCopy` before calling
13651365
`Py_DECREF`` in order copy any changes back to ``base`` and
@@ -3259,12 +3259,14 @@ Memory management
32593259
.. c:function:: int PyArray_ResolveWritebackIfCopy(PyArrayObject* obj)
32603260
32613261
If ``obj.flags`` has :c:data:`NPY_ARRAY_WRITEBACKIFCOPY` or (deprecated)
3262-
:c:data:`NPY_ARRAY_UPDATEIFCOPY`, this function copies ``obj->data`` to
3263-
`obj->base->data`, clears the flags, `DECREF` s `obj->base` and makes it
3264-
writeable, and sets ``obj->base`` to NULL. This is the opposite of
3262+
:c:data:`NPY_ARRAY_UPDATEIFCOPY`, this function clears the flags, `DECREF` s
3263+
`obj->base` and makes it writeable, and sets ``obj->base`` to NULL. It then
3264+
copies ``obj->data`` to `obj->base->data`, and returns the error state of
3265+
the copy operation. This is the opposite of
32653266
:c:func:`PyArray_SetWritebackIfCopyBase`. Usually this is called once
32663267
you are finished with ``obj``, just before ``Py_DECREF(obj)``. It may be called
3267-
multiple times, or with ``NULL`` input.
3268+
multiple times, or with ``NULL`` input. See also
3269+
:c:func:`PyArray_DiscardWritebackIfCopy`.
32683270
32693271
Returns 0 if nothing was done, -1 on error, and 1 if action was taken.
32703272
@@ -3486,12 +3488,14 @@ Miscellaneous Macros
34863488
34873489
.. c:function:: PyArray_DiscardWritebackIfCopy(PyObject* obj)
34883490
3489-
Reset the :c:data:`NPY_ARRAY_WRITEBACKIFCOPY` and deprecated
3490-
:c:data:`NPY_ARRAY_UPDATEIFCOPY` flag. Resets the
3491-
:c:data:`NPY_ARRAY_WRITEABLE` flag on the base object. It also
3492-
discards pending changes to the base object. This is
3493-
useful for recovering from an error condition when
3494-
writeback semantics are used.
3491+
If ``obj.flags`` has :c:data:`NPY_ARRAY_WRITEBACKIFCOPY` or (deprecated)
3492+
:c:data:`NPY_ARRAY_UPDATEIFCOPY`, this function clears the flags, `DECREF` s
3493+
`obj->base` and makes it writeable, and sets ``obj->base`` to NULL. In
3494+
contrast to :c:func:`PyArray_DiscardWritebackIfCopy` it makes no attempt
3495+
to copy the data from `obj->base` This undoes
3496+
:c:func:`PyArray_SetWritebackIfCopyBase`. Usually this is called after an
3497+
error when you are finished with ``obj``, just before ``Py_DECREF(obj)``.
3498+
It may be called multiple times, or with ``NULL`` input.
34953499
34963500
.. c:function:: PyArray_XDECREF_ERR(PyObject* obj)
34973501

numpy/core/include/numpy/ndarrayobject.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,17 @@ extern "C" CONFUSE_EMACS
170170
(k)*PyArray_STRIDES(obj)[2] + \
171171
(l)*PyArray_STRIDES(obj)[3]))
172172

173+
/* Move to arrayobject.c once PyArray_XDECREF_ERR is removed */
173174
static NPY_INLINE void
174175
PyArray_DiscardWritebackIfCopy(PyArrayObject *arr)
175176
{
176-
if (arr != NULL) {
177-
if ((PyArray_FLAGS(arr) & NPY_ARRAY_WRITEBACKIFCOPY) ||
178-
(PyArray_FLAGS(arr) & NPY_ARRAY_UPDATEIFCOPY)) {
179-
PyArrayObject *base = (PyArrayObject *)PyArray_BASE(arr);
180-
PyArray_ENABLEFLAGS(base, NPY_ARRAY_WRITEABLE);
177+
PyArrayObject_fields *fa = (PyArrayObject_fields *)arr;
178+
if (fa && fa->base) {
179+
if ((fa->flags & NPY_ARRAY_UPDATEIFCOPY) ||
180+
(fa->flags & NPY_ARRAY_WRITEBACKIFCOPY)) {
181+
PyArray_ENABLEFLAGS((PyArrayObject*)fa->base, NPY_ARRAY_WRITEABLE);
182+
Py_DECREF(fa->base);
183+
fa->base = NULL;
181184
PyArray_CLEARFLAGS(arr, NPY_ARRAY_WRITEBACKIFCOPY);
182185
PyArray_CLEARFLAGS(arr, NPY_ARRAY_UPDATEIFCOPY);
183186
}

numpy/core/src/multiarray/multiarray_tests.c.src

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,18 @@ npy_resolve(PyObject* NPY_UNUSED(self), PyObject* args)
667667
Py_RETURN_NONE;
668668
}
669669

670+
/* resolve WRITEBACKIFCOPY */
671+
static PyObject*
672+
npy_discard(PyObject* NPY_UNUSED(self), PyObject* args)
673+
{
674+
if (!PyArray_Check(args)) {
675+
PyErr_SetString(PyExc_TypeError, "test needs ndarray input");
676+
return NULL;
677+
}
678+
PyArray_DiscardWritebackIfCopy((PyArrayObject*)args);
679+
Py_RETURN_NONE;
680+
}
681+
670682
#if !defined(NPY_PY3K)
671683
static PyObject *
672684
int_subclass(PyObject *dummy, PyObject *args)
@@ -1765,6 +1777,9 @@ static PyMethodDef Multiarray_TestsMethods[] = {
17651777
{"npy_resolve",
17661778
npy_resolve,
17671779
METH_O, NULL},
1780+
{"npy_discard",
1781+
npy_discard,
1782+
METH_O, NULL},
17681783
#if !defined(NPY_PY3K)
17691784
{"test_int_subclass",
17701785
int_subclass,

numpy/core/tests/test_multiarray.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7266,19 +7266,47 @@ def test_dot_out(self):
72667266

72677267
def test_view_assign(self):
72687268
from numpy.core.multiarray_tests import npy_create_writebackifcopy, npy_resolve
7269+
72697270
arr = np.arange(9).reshape(3, 3).T
72707271
arr_wb = npy_create_writebackifcopy(arr)
72717272
assert_(arr_wb.flags.writebackifcopy)
72727273
assert_(arr_wb.base is arr)
7273-
arr_wb[:] = -100
7274+
arr_wb[...] = -100
72747275
npy_resolve(arr_wb)
7276+
# arr changes after resolve, even though we assigned to arr_wb
72757277
assert_equal(arr, -100)
7276-
# after resolve, the two arrays no longer reference eachother
7277-
assert_(not arr_wb.ctypes.data == 0)
7278-
arr_wb[:] = 100
7278+
# after resolve, the two arrays no longer reference each other
7279+
assert_(arr_wb.ctypes.data != 0)
7280+
assert_equal(arr_wb.base, None)
7281+
# assigning to arr_wb does not get transfered to arr
7282+
arr_wb[...] = 100
72797283
assert_equal(arr, -100)
72807284

72817285

7286+
def test_view_discard_refcount(self):
7287+
from numpy.core.multiarray_tests import npy_create_writebackifcopy, npy_discard
7288+
7289+
arr = np.arange(9).reshape(3, 3).T
7290+
orig = arr.copy()
7291+
if HAS_REFCOUNT:
7292+
arr_cnt = sys.getrefcount(arr)
7293+
arr_wb = npy_create_writebackifcopy(arr)
7294+
assert_(arr_wb.flags.writebackifcopy)
7295+
assert_(arr_wb.base is arr)
7296+
arr_wb[...] = -100
7297+
npy_discard(arr_wb)
7298+
# arr remains unchanged after discard
7299+
assert_equal(arr, orig)
7300+
# after discard, the two arrays no longer reference each other
7301+
assert_(arr_wb.ctypes.data != 0)
7302+
assert_equal(arr_wb.base, None)
7303+
if HAS_REFCOUNT:
7304+
assert_equal(arr_cnt, sys.getrefcount(arr))
7305+
# assigning to arr_wb does not get transfered to arr
7306+
arr_wb[...] = 100
7307+
assert_equal(arr, orig)
7308+
7309+
72827310
class TestArange(object):
72837311
def test_infinite(self):
72847312
assert_raises_regex(

0 commit comments

Comments
 (0)
0