diff options
author | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2022-01-07 11:04:08 -0500 |
---|---|---|
committer | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2022-01-08 17:44:29 -0500 |
commit | 99d927b68a6b4df51600509d5a4a92687faf8c73 (patch) | |
tree | 878e25adfea4acb7eb7c892496205298cef1f704 /numpy | |
parent | b375f4eace5636d87fb6e09de23e8b2cb5259a1e (diff) | |
download | numpy-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.c | 16 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 26 |
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 |