summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2022-01-10 20:37:29 -0600
committerGitHub <noreply@github.com>2022-01-10 20:37:29 -0600
commitacf33eb2a8a3de148cc523db13fab633530125e8 (patch)
tree1784f958dbea37a2f4dbf160d661e932da0cec5a /numpy
parentb2a97ba5d257c4ea8312bb23e1c9a4b9d41336a9 (diff)
parent84b8f61aeb179a35e03c165aa2cbe2f0a90df857 (diff)
downloadnumpy-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__.pyi2
-rw-r--r--numpy/core/_add_newdocs.py16
-rw-r--r--numpy/core/src/multiarray/ctors.c29
-rw-r--r--numpy/core/src/multiarray/getset.c13
-rw-r--r--numpy/core/src/multiarray/methods.c14
-rw-r--r--numpy/core/tests/test_deprecations.py11
-rw-r--r--numpy/core/tests/test_multiarray.py28
-rw-r--r--numpy/ma/tests/test_subclassing.py5
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):