Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ENH: Make ndarray.__array_finalize__ a callable no-op #20766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/release/upcoming_changes/20766.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Setting ``__array_finalize__`` to ``None`` is deprecated. It must now be
a method and may wish to call ``super().__array_finalize__(obj)`` after
checking for ``None`` or if the NumPy version is sufficiently new.
5 changes: 5 additions & 0 deletions doc/release/upcoming_changes/20766.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
``ndarray.__array_finalize__`` is now callable
----------------------------------------------
This means subclasses can now use ``super().__array_finalize__(obj)``
without worrying whether ``ndarray`` is their superclass or not.
The actual call remains a no-op.
2 changes: 1 addition & 1 deletion numpy/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 10 additions & 6 deletions numpy/core/_add_newdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2265,10 +2265,6 @@
"""Array protocol: Python side."""))


add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_finalize__',
"""None."""))


add_newdoc('numpy.core.multiarray', 'ndarray', ('__array_priority__',
"""Array priority."""))

Expand All @@ -2278,12 +2274,12 @@

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',
Expand Down Expand Up @@ -2811,6 +2807,14 @@
"""))


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], /)

Expand Down
29 changes: 26 additions & 3 deletions numpy/core/src/multiarray/ctors.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down
13 changes: 0 additions & 13 deletions numpy/core/src/multiarray/getset.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 */
};

Expand Down
14 changes: 12 additions & 2 deletions numpy/core/src/multiarray/methods.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) ||
Expand All @@ -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(
Expand Down Expand Up @@ -925,6 +925,13 @@ array_astype(PyArrayObject *self,
/* default sub-type implementation */


static PyObject *
array_finalizearray(PyArrayObject *self, PyObject *obj)
{
Py_RETURN_NONE;
}


static PyObject *
array_wraparray(PyArrayObject *self, PyObject *args)
{
Expand Down Expand Up @@ -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},
Expand Down
11 changes: 11 additions & 0 deletions numpy/core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1239,3 +1239,14 @@ def test_mem_seteventhook(self):
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))
28 changes: 26 additions & 2 deletions numpy/core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -8954,12 +8954,28 @@ def __array_finalize__(self, obj):
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)

Expand Down Expand Up @@ -8997,6 +9013,14 @@ class Dummy: pass
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
Expand Down Expand Up @@ -9201,7 +9225,7 @@ def test_smaller_dtype_multiple(self):
# 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)
Expand Down
5 changes: 2 additions & 3 deletions numpy/ma/tests/test_subclassing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ def __new__(cls,arr,info={}):
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

Expand Down Expand Up @@ -315,7 +314,7 @@ def test_subclass_repr(self):
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):
Expand Down