summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorMarten van Kerkwijk <mhvk@astro.utoronto.ca>2022-01-07 11:04:08 -0500
committerMarten van Kerkwijk <mhvk@astro.utoronto.ca>2022-01-08 17:44:29 -0500
commit99d927b68a6b4df51600509d5a4a92687faf8c73 (patch)
tree878e25adfea4acb7eb7c892496205298cef1f704 /numpy
parentb375f4eace5636d87fb6e09de23e8b2cb5259a1e (diff)
downloadnumpy-99d927b68a6b4df51600509d5a4a92687faf8c73.tar.gz
MAINT: Speed up subtypes that do not override __array_finalize__
In the process, __array_finalized__ is looked up on the subclass instead of the instance, which is more like python for methods like these. It cannot make a difference, since the instance is created in the same routine, so the instance method is guaranteed to be the same as that on the class.
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/multiarray/ctors.c16
-rw-r--r--numpy/core/tests/test_multiarray.py26
2 files changed, 37 insertions, 5 deletions
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index e60cacc18..2ce648998 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -875,15 +875,23 @@ 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 == Py_None) {
+ else if (func == ndarray_array_finalize || func == Py_None) {
Py_DECREF(func);
}
else {
@@ -903,7 +911,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/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index d8a8c4da4..8f4851036 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)
@@ -9005,6 +9021,14 @@ class TestArrayFinalize:
a = np.array(1).view(SuperFinalize)
assert_(a.saved_result is None)
+ def test_can_use_none(self):
+ # For backward compatibility, to show nothing needs finalizing.
+ class NoFinalize(np.ndarray):
+ __array_finalize__ = None
+
+ a = np.array(1).view(NoFinalize)
+ assert isinstance(a, NoFinalize)
+
def test_orderconverter_with_nonASCII_unicode_ordering():
# gh-7475