diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2022-01-10 20:37:29 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-10 20:37:29 -0600 |
commit | acf33eb2a8a3de148cc523db13fab633530125e8 (patch) | |
tree | 1784f958dbea37a2f4dbf160d661e932da0cec5a /numpy | |
parent | b2a97ba5d257c4ea8312bb23e1c9a4b9d41336a9 (diff) | |
parent | 84b8f61aeb179a35e03c165aa2cbe2f0a90df857 (diff) | |
download | numpy-acf33eb2a8a3de148cc523db13fab633530125e8.tar.gz |
Merge pull request #20766 from mhvk/ndarray_array_finalize
ENH: Make ndarray.__array_finalize__ a callable no-op
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/__init__.pyi | 2 | ||||
-rw-r--r-- | numpy/core/_add_newdocs.py | 16 | ||||
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 29 | ||||
-rw-r--r-- | numpy/core/src/multiarray/getset.c | 13 | ||||
-rw-r--r-- | numpy/core/src/multiarray/methods.c | 14 | ||||
-rw-r--r-- | numpy/core/tests/test_deprecations.py | 11 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 28 | ||||
-rw-r--r-- | numpy/ma/tests/test_subclassing.py | 5 |
8 files changed, 88 insertions, 30 deletions
diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi index b60d47790..8e92e0f42 100644 --- a/numpy/__init__.pyi +++ b/numpy/__init__.pyi @@ -1496,7 +1496,7 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]): ) -> Any: ... @property - def __array_finalize__(self) -> None: ... + def __array_finalize__(self, obj: None | NDArray[Any], /) -> None: ... def __array_wrap__( self, diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py index a8d73af3f..219383e1e 100644 --- a/numpy/core/_add_newdocs.py +++ b/numpy/core/_add_newdocs.py @@ -2265,10 +2265,6 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_interface__', """Array protocol: Python side.""")) -add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_finalize__', - """None.""")) - - add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_priority__', """Array priority.""")) @@ -2278,12 +2274,12 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_struct__', add_newdoc('numpy.core.multiarray', 'ndarray', ('__dlpack__', """a.__dlpack__(*, stream=None) - + DLPack Protocol: Part of the Array API.""")) add_newdoc('numpy.core.multiarray', 'ndarray', ('__dlpack_device__', """a.__dlpack_device__() - + DLPack Protocol: Part of the Array API.""")) add_newdoc('numpy.core.multiarray', 'ndarray', ('base', @@ -2811,6 +2807,14 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('__array__', """)) +add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_finalize__', + """a.__array_finalize__(obj, /) + + Present so subclasses can call super. Does nothing. + + """)) + + add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_prepare__', """a.__array_prepare__(array[, context], /) diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index e60cacc18..25eb91977 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -875,16 +875,39 @@ PyArray_NewFromDescr_int( /* * call the __array_finalize__ method if a subtype was requested. * If obj is NULL use Py_None for the Python callback. + * For speed, we skip if __array_finalize__ is inherited from ndarray + * (since that function does nothing), or, for backward compatibility, + * if it is None. */ if (subtype != &PyArray_Type) { PyObject *res, *func; - - func = PyObject_GetAttr((PyObject *)fa, npy_ma_str_array_finalize); + static PyObject *ndarray_array_finalize = NULL; + /* First time, cache ndarray's __array_finalize__ */ + if (ndarray_array_finalize == NULL) { + ndarray_array_finalize = PyObject_GetAttr( + (PyObject *)&PyArray_Type, npy_ma_str_array_finalize); + } + func = PyObject_GetAttr((PyObject *)subtype, npy_ma_str_array_finalize); if (func == NULL) { goto fail; } + else if (func == ndarray_array_finalize) { + Py_DECREF(func); + } else if (func == Py_None) { Py_DECREF(func); + /* + * 2022-01-08, NumPy 1.23; when deprecation period is over, remove this + * whole stanza so one gets a "NoneType object is not callable" TypeError. + */ + if (DEPRECATE( + "Setting __array_finalize__ = None to indicate no finalization" + "should be done is deprecated. Instead, just inherit from " + "ndarray or, if that is not possible, explicitly set to " + "ndarray.__array_function__; this will raise a TypeError " + "in the future. (Deprecated since NumPy 1.23)") < 0) { + goto fail; + } } else { if (PyCapsule_CheckExact(func)) { @@ -903,7 +926,7 @@ PyArray_NewFromDescr_int( if (obj == NULL) { obj = Py_None; } - res = PyObject_CallFunctionObjArgs(func, obj, NULL); + res = PyObject_CallFunctionObjArgs(func, (PyObject *)fa, obj, NULL); Py_DECREF(func); if (res == NULL) { goto fail; diff --git a/numpy/core/src/multiarray/getset.c b/numpy/core/src/multiarray/getset.c index ac6465acd..a4f972ba4 100644 --- a/numpy/core/src/multiarray/getset.c +++ b/numpy/core/src/multiarray/getset.c @@ -926,15 +926,6 @@ array_transpose_get(PyArrayObject *self, void *NPY_UNUSED(ignored)) return PyArray_Transpose(self, NULL); } -/* If this is None, no function call is made - --- default sub-class behavior -*/ -static PyObject * -array_finalize_get(PyArrayObject *NPY_UNUSED(self), void *NPY_UNUSED(ignored)) -{ - Py_RETURN_NONE; -} - NPY_NO_EXPORT PyGetSetDef array_getsetlist[] = { {"ndim", (getter)array_ndim_get, @@ -1008,10 +999,6 @@ NPY_NO_EXPORT PyGetSetDef array_getsetlist[] = { (getter)array_priority_get, NULL, NULL, NULL}, - {"__array_finalize__", - (getter)array_finalize_get, - NULL, - NULL, NULL}, {NULL, NULL, NULL, NULL, NULL}, /* Sentinel */ }; diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 8dd7c112f..33f78dff2 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -859,7 +859,7 @@ array_astype(PyArrayObject *self, * and it's not a subtype if subok is False, then we * can skip the copy. */ - if (forcecopy != NPY_COPY_ALWAYS && + if (forcecopy != NPY_COPY_ALWAYS && (order == NPY_KEEPORDER || (order == NPY_ANYORDER && (PyArray_IS_C_CONTIGUOUS(self) || @@ -881,7 +881,7 @@ array_astype(PyArrayObject *self, Py_DECREF(dtype); return NULL; } - + if (!PyArray_CanCastArrayTo(self, dtype, casting)) { PyErr_Clear(); npy_set_invalid_cast_error( @@ -926,6 +926,13 @@ array_astype(PyArrayObject *self, static PyObject * +array_finalizearray(PyArrayObject *self, PyObject *obj) +{ + Py_RETURN_NONE; +} + + +static PyObject * array_wraparray(PyArrayObject *self, PyObject *args) { PyArrayObject *arr; @@ -2777,6 +2784,9 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { {"__array_prepare__", (PyCFunction)array_preparearray, METH_VARARGS, NULL}, + {"__array_finalize__", + (PyCFunction)array_finalizearray, + METH_O, NULL}, {"__array_wrap__", (PyCFunction)array_wraparray, METH_VARARGS, NULL}, diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 76486f755..d2a69d4cf 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -1239,3 +1239,14 @@ class TestMemEventHook(_DeprecationTestCase): with pytest.warns(DeprecationWarning, match='PyDataMem_SetEventHook is deprecated'): ma_tests.test_pydatamem_seteventhook_end() + + +class TestArrayFinalizeNone(_DeprecationTestCase): + message = "Setting __array_finalize__ = None" + + def test_use_none_is_deprecated(self): + # Deprecated way that ndarray itself showed nothing needs finalizing. + class NoFinalize(np.ndarray): + __array_finalize__ = None + + self.assert_deprecated(lambda: np.array(1).view(NoFinalize)) diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 2529705d5..708e82910 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -8954,12 +8954,28 @@ class TestArrayFinalize: a = np.array(1).view(SavesBase) assert_(a.saved_base is a.base) - def test_bad_finalize(self): + def test_bad_finalize1(self): class BadAttributeArray(np.ndarray): @property def __array_finalize__(self): raise RuntimeError("boohoo!") + with pytest.raises(TypeError, match="not callable"): + np.arange(10).view(BadAttributeArray) + + def test_bad_finalize2(self): + class BadAttributeArray(np.ndarray): + def __array_finalize__(self): + raise RuntimeError("boohoo!") + + with pytest.raises(TypeError, match="takes 1 positional"): + np.arange(10).view(BadAttributeArray) + + def test_bad_finalize3(self): + class BadAttributeArray(np.ndarray): + def __array_finalize__(self, obj): + raise RuntimeError("boohoo!") + with pytest.raises(RuntimeError, match="boohoo!"): np.arange(10).view(BadAttributeArray) @@ -8997,6 +9013,14 @@ class TestArrayFinalize: break_cycles() assert_(obj_ref() is None, "no references should remain") + def test_can_use_super(self): + class SuperFinalize(np.ndarray): + def __array_finalize__(self, obj): + self.saved_result = super().__array_finalize__(obj) + + a = np.array(1).view(SuperFinalize) + assert_(a.saved_result is None) + def test_orderconverter_with_nonASCII_unicode_ordering(): # gh-7475 @@ -9201,7 +9225,7 @@ class TestViewDtype: # x is non-contiguous x = np.arange(10, dtype='<i4')[::2] with pytest.raises(ValueError, - match='the last axis must be contiguous'): + match='the last axis must be contiguous'): x.view('<i2') expected = [[0, 0], [2, 0], [4, 0], [6, 0], [8, 0]] assert_array_equal(x[:, np.newaxis].view('<i2'), expected) diff --git a/numpy/ma/tests/test_subclassing.py b/numpy/ma/tests/test_subclassing.py index 83a9b2f51..3491cef7f 100644 --- a/numpy/ma/tests/test_subclassing.py +++ b/numpy/ma/tests/test_subclassing.py @@ -28,8 +28,7 @@ class SubArray(np.ndarray): return x def __array_finalize__(self, obj): - if callable(getattr(super(), '__array_finalize__', None)): - super().__array_finalize__(obj) + super().__array_finalize__(obj) self.info = getattr(obj, 'info', {}).copy() return @@ -315,7 +314,7 @@ class TestSubclassing: assert_startswith(repr(mx), 'masked_array') xsub = SubArray(x) mxsub = masked_array(xsub, mask=[True, False, True, False, False]) - assert_startswith(repr(mxsub), + assert_startswith(repr(mxsub), f'masked_{SubArray.__name__}(data=[--, 1, --, 3, 4]') def test_subclass_str(self): |