8000 MAINT: Speed up subtypes that do not override __array_finalize__ · numpy/numpy@99d927b · GitHub
[go: up one dir, main page]

Skip to content

Commit 99d927b

Browse files
committed
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.
1 parent b375f4e commit 99d927b

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

numpy/core/src/multiarray/ctors.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -875,15 +875,23 @@ PyArray_NewFromDescr_int(
875875
/*
876876
* call the __array_finalize__ method if a subtype was requested.
877877
* If obj is NULL use Py_None for the Python callback.
878+
* For speed, we skip if __array_finalize__ is inherited from ndarray
879+
* (since that function does nothing), or, for backward compatibility,
880+
* if it is None.
878881
*/
879882
if (subtype != &PyArray_Type) {
880883
PyObject *res, *func;
881-
882-
func = PyObject_GetAttr((PyObject *)fa, npy_ma_str_array_finalize);
884+
static PyObject *ndarray_array_finalize = NULL;
885+
/* First time, cache ndarray's __array_finalize__ */
886+
if (ndarray_array_finalize == NULL) {
887+
ndarray_array_finalize = PyObject_GetAttr(
888+
(PyObject *)&PyArray_Type, npy_ma_str_array_finalize);
889+
}
890+
func = PyObject_GetAttr((PyObject *)subtype, npy_ma_str_array_finalize);
883891
if (func == NULL) {
884892
goto fail;
885893
}
886-
else if (func == Py_None) {
894+
else if (func == ndarray_array_finalize || func == Py_None) {
887895
Py_DECREF(func);
888896
}
889897
else {
@@ -903,7 +911,7 @@ PyArray_NewFromDescr_int(
903911
if (obj == NULL) {
904912
obj = Py_None;
905913
}
906-
res = PyObject_CallFunctionObjArgs(func, obj, NULL);
914+
res = PyObject_CallFunctionObjArgs(func, (PyObject *)fa, obj, NULL);
907915
Py_DECREF(func);
908916
if (res == NULL) {
909917
goto fail;

numpy/core/tests/test_multiarray.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8954,12 +8954,28 @@ def __array_finalize__(self, obj):
89548954
a = np.array(1).view(SavesBase)
89558955
assert_(a.saved_base is a.base)
89568956

8957-
def test_bad_finalize(self):
8957+
def test_bad_finalize1(self):
89588958
class BadAttributeArray(np.ndarray):
89598959
@property
89608960
def __array_finalize__(self):
89618961
raise RuntimeError("boohoo!")
89628962

8963+
with pytest.raises(TypeError, match="not callable"):
8964+
np.arange(10).view(BadAttributeArray)
8965+
8966+
def test_bad_finalize2(self):
8967+
class BadAttributeArray(np.ndarray):
8968+
def __array_finalize__(self):
8969+
raise RuntimeError("boohoo!")
8970+
8971+
with pytest.raises(TypeError, match="takes 1 positional"):
8972+
np.arange(10).view(BadAttributeArray)
8973+
8974+
def test_bad_finalize3(self):
8975+
class BadAttributeArray(np.ndarray):
8976+
def __array_finalize__(self, obj):
8977+
raise RuntimeError("boohoo!")
8978+
89638979
with pytest.raises(RuntimeError, match="boohoo!"):
89648980
np.arange(10).view(BadAttributeArray)
89658981

@@ -9005,6 +9021,14 @@ def __array_finalize__(self, obj):
90059021
a = np.array(1).view(SuperFinalize)
90069022
assert_(a.saved_result is None)
90079023

9024+
def test_can_use_none(self):
9025+
# For backward compatibility, to show nothing needs finalizing.
9026+
class NoFinalize(np.ndarray):
9027+
__array_finalize__ = None
9028+
9029+
a = np.array(1).view(NoFinalize)
9030+
assert isinstance(a, NoFinalize)
9031+
90089032

90099033
def test_orderconverter_with_nonASCII_unicode_ordering():
90109034
# gh-7475

0 commit comments

Comments
 (0)
0