Skip to content

Commit 11eb606

Browse files
authored
Merge pull request numpy#27119 from seberg/use-optional-attr
ENH: Use ``PyObject_GetOptionalAttr``
2 parents 69e2845 + 37eb75a commit 11eb606

File tree

10 files changed

+74
-75
lines changed

10 files changed

+74
-75
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
* NumPy now uses fast-on-failure attribute lookups for protocols.
2+
This can greatly reduce overheads of function calls or array creation
3+
especially with custom Python objects. The largest improvements
4+
will be seen on Python 3.12 or newer.

numpy/_core/src/common/binop_override.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,15 @@ binop_should_defer(PyObject *self, PyObject *other, int inplace)
129129
* Classes with __array_ufunc__ are living in the future, and only need to
130130
* check whether __array_ufunc__ equals None.
131131
*/
132-
attr = PyArray_LookupSpecial(other, npy_interned_str.array_ufunc);
133-
if (attr != NULL) {
132+
if (PyArray_LookupSpecial(other, npy_interned_str.array_ufunc, &attr) < 0) {
133+
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
134+
}
135+
else if (attr != NULL) {
134136
defer = !inplace && (attr == Py_None);
135137
Py_DECREF(attr);
136138
return defer;
137139
}
138-
else if (PyErr_Occurred()) {
139-
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
140-
}
140+
141141
/*
142142
* Otherwise, we need to check for the legacy __array_priority__. But if
143143
* other.__class__ is a subtype of self.__class__, then it's already had

numpy/_core/src/common/get_attr_string.h

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
#define NUMPY_CORE_SRC_COMMON_GET_ATTR_STRING_H_
33

44
#include <Python.h>
5-
#include "ufunc_object.h"
5+
#include "npy_pycompat.h"
6+
67

78
static inline npy_bool
89
_is_basic_python_type(PyTypeObject *tp)
@@ -44,24 +45,21 @@ _is_basic_python_type(PyTypeObject *tp)
4445
* Assumes that the special method is a numpy-specific one, so does not look
4546
* at builtin types. It does check base ndarray and numpy scalar types.
4647
*
47-
* In future, could be made more like _Py_LookupSpecial
48+
* It may make sense to just replace this with `PyObject_GetOptionalAttr`.
4849
*/
49-
static inline PyObject *
50-
PyArray_LookupSpecial(PyObject *obj, PyObject *name_unicode)
50+
static inline int
51+
PyArray_LookupSpecial(
52+
PyObject *obj, PyObject *name_unicode, PyObject **res)
5153
{
5254
PyTypeObject *tp = Py_TYPE(obj);
5355

5456
/* We do not need to check for special attributes on trivial types */
5557
if (_is_basic_python_type(tp)) {
56-
return NULL;
57-
}
58-
PyObject *res = PyObject_GetAttr((PyObject *)tp, name_unicode);
59-
60-
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
61-
PyErr_Clear();
58+
*res = NULL;
59+
return 0;
6260
}
6361

64-
return res;
62+
return PyObject_GetOptionalAttr((PyObject *)tp, name_unicode, res);
6563
}
6664

6765

@@ -73,23 +71,20 @@ PyArray_LookupSpecial(PyObject *obj, PyObject *name_unicode)
7371
*
7472
* Kept for backwards compatibility. In future, we should deprecate this.
7573
*/
76-
static inline PyObject *
77-
PyArray_LookupSpecial_OnInstance(PyObject *obj, PyObject *name_unicode)
74+
static inline int
75+
PyArray_LookupSpecial_OnInstance(
76+
PyObject *obj, PyObject *name_unicode, PyObject **res)
7877
{
7978
PyTypeObject *tp = Py_TYPE(obj);
8079

8180
/* We do not need to check for special attributes on trivial types */
81+
/* Note: This check should likely be reduced on Python 3.13+ */
8282
if (_is_basic_python_type(tp)) {
83-
return NULL;
84-
}
85-
86-
PyObject *res = PyObject_GetAttr(obj, name_unicode);
87-
88-
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
89-
PyErr_Clear();
83+
*res = NULL;
84+
return 0;
9085
}
9186

92-
return res;
87+
return PyObject_GetOptionalAttr(obj, name_unicode, res);
9388
}
9489

9590
#endif /* NUMPY_CORE_SRC_COMMON_GET_ATTR_STRING_H_ */

numpy/_core/src/common/ufunc_override.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
22
#define _MULTIARRAYMODULE
33

4+
#include "numpy/ndarrayobject.h"
45
#include "numpy/ndarraytypes.h"
56
#include "npy_pycompat.h"
67
#include "get_attr_string.h"
@@ -35,14 +36,12 @@ PyUFuncOverride_GetNonDefaultArrayUfunc(PyObject *obj)
3536
* Does the class define __array_ufunc__? (Note that LookupSpecial has fast
3637
* return for basic python types, so no need to worry about those here)
3738
*/
38-
cls_array_ufunc = PyArray_LookupSpecial(obj, npy_interned_str.array_ufunc);
39-
if (cls_array_ufunc == NULL) {
40-
if (PyErr_Occurred()) {
41-
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
42-
}
39+
if (PyArray_LookupSpecial(
40+
obj, npy_interned_str.array_ufunc, &cls_array_ufunc) < 0) {
41+
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
4342
return NULL;
4443
}
45-
/* Ignore if the same as ndarray.__array_ufunc__ */
44+
/* Ignore if the same as ndarray.__array_ufunc__ (it may be NULL here) */
4645
if (cls_array_ufunc == npy_static_pydata.ndarray_array_ufunc) {
4746
Py_DECREF(cls_array_ufunc);
4847
return NULL;

numpy/_core/src/multiarray/arrayfunction_override.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <Python.h>
55
#include "structmember.h"
66

7+
#include "numpy/ndarrayobject.h"
78
#include "numpy/ndarraytypes.h"
89
#include "get_attr_string.h"
910
#include "npy_import.h"
@@ -25,8 +26,9 @@ get_array_function(PyObject *obj)
2526
return npy_static_pydata.ndarray_array_function;
2627
}
2728

28-
PyObject *array_function = PyArray_LookupSpecial(obj, npy_interned_str.array_function);
29-
if (array_function == NULL && PyErr_Occurred()) {
29+
PyObject *array_function;
30+
if (PyArray_LookupSpecial(
31+
obj, npy_interned_str.array_function, &array_function) < 0) {
3032
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
3133
}
3234

numpy/_core/src/multiarray/arraywrap.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ npy_find_array_wrap(
5757
}
5858
}
5959
else {
60-
PyObject *new_wrap = PyArray_LookupSpecial_OnInstance(obj, npy_interned_str.array_wrap);
61-
if (new_wrap == NULL) {
62-
if (PyErr_Occurred()) {
63-
goto fail;
64-
}
60+
PyObject *new_wrap;
61+
if (PyArray_LookupSpecial_OnInstance(
62+
obj, npy_interned_str.array_wrap, &new_wrap) < 0) {
63+
goto fail;
64+
}
65+
else if (new_wrap == NULL) {
6566
continue;
6667
}
6768
double curr_priority = PyArray_GetPriority(obj, 0);
@@ -159,15 +160,14 @@ npy_apply_wrap(
159160
}
160161
else {
161162
/* Replace passed wrap/wrap_type (borrowed refs) with new_wrap/type. */
162-
new_wrap = PyArray_LookupSpecial_OnInstance(
163-
original_out, npy_interned_str.array_wrap);
164-
if (new_wrap != NULL) {
163+
if (PyArray_LookupSpecial_OnInstance(
164+
original_out, npy_interned_str.array_wrap, &new_wrap) < 0) {
165+
return NULL;
166+
}
167+
else if (new_wrap != NULL) {
165168
wrap = new_wrap;
166169
wrap_type = (PyObject *)Py_TYPE(original_out);
167170
}
168-
else if (PyErr_Occurred()) {
169-
return NULL;
170-
}
171171
}
172172
}
173173
/*

numpy/_core/src/multiarray/ctors.c

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,13 +2036,12 @@ PyArray_FromStructInterface(PyObject *input)
20362036
PyObject *attr;
20372037
char endian = NPY_NATBYTE;
20382038

2039-
attr = PyArray_LookupSpecial_OnInstance(input, npy_interned_str.array_struct);
2040-
if (attr == NULL) {
2041-
if (PyErr_Occurred()) {
2042-
return NULL;
2043-
} else {
2044-
return Py_NotImplemented;
2045-
}
2039+
if (PyArray_LookupSpecial_OnInstance(
2040+
input, npy_interned_str.array_struct, &attr) < 0) {
2041+
return NULL;
2042+
}
2043+
else if (attr == NULL) {
2044+
return Py_NotImplemented;
20462045
}
20472046
if (!PyCapsule_CheckExact(attr)) {
20482047
if (PyType_Check(input) && PyObject_HasAttrString(attr, "__get__")) {
@@ -2160,12 +2159,11 @@ PyArray_FromInterface(PyObject *origin)
21602159
npy_intp dims[NPY_MAXDIMS], strides[NPY_MAXDIMS];
21612160
int dataflags = NPY_ARRAY_BEHAVED;
21622161

2163-
iface = PyArray_LookupSpecial_OnInstance(origin, npy_interned_str.array_interface);
2164-
2165-
if (iface == NULL) {
2166-
if (PyErr_Occurred()) {
2167-
return NULL;
2168-
}
2162+
if (PyArray_LookupSpecial_OnInstance(
2163+
origin, npy_interned_str.array_interface, &iface) < 0) {
2164+
return NULL;
2165+
}
2166+
else if (iface == NULL) {
21692167
return Py_NotImplemented;
21702168
}
21712169
if (!PyDict_Check(iface)) {
@@ -2515,11 +2513,11 @@ PyArray_FromArrayAttr_int(PyObject *op, PyArray_Descr *descr, int copy,
25152513
PyObject *new;
25162514
PyObject *array_meth;
25172515

2518-
array_meth = PyArray_LookupSpecial_OnInstance(op, npy_interned_str.array);
2519-
if (array_meth == NULL) {
2520-
if (PyErr_Occurred()) {
2521-
return NULL;
2522-
}
2516+
if (PyArray_LookupSpecial_OnInstance(
2517+
op, npy_interned_str.array, &array_meth) < 0) {
2518+
return NULL;
2519+
}
2520+
else if (array_meth == NULL) {
25232521
return Py_NotImplemented;
25242522
}
25252523

numpy/_core/src/multiarray/multiarraymodule.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,13 @@ PyArray_GetPriority(PyObject *obj, double default_)
157157
return NPY_SCALAR_PRIORITY;
158158
}
159159

160-
ret = PyArray_LookupSpecial_OnInstance(obj, npy_interned_str.array_priority);
161-
if (ret == NULL) {
162-
if (PyErr_Occurred()) {
163-
/* TODO[gh-14801]: propagate crashes during attribute access? */
164-
PyErr_Clear();
165-
}
160+
if (PyArray_LookupSpecial_OnInstance(
161+
obj, npy_interned_str.array_priority, &ret) < 0) {
162+
/* TODO[gh-14801]: propagate crashes during attribute access? */
163+
PyErr_Clear();
164+
return default_;
165+
}
166+
else if (ret == NULL) {
166167
return default_;
167168
}
168169

numpy/_core/src/multiarray/scalartypes.c.src

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,15 @@ find_binary_operation_path(
209209
* our ufuncs without preventing recursion.
210210
* It may be nice to avoid double lookup in `BINOP_GIVE_UP_IF_NEEDED`.
211211
*/
212-
PyObject *attr = PyArray_LookupSpecial(other, npy_interned_str.array_ufunc);
213-
if (attr != NULL) {
212+
PyObject *attr;
213+
if (PyArray_LookupSpecial(other, npy_interned_str.array_ufunc, &attr) < 0) {
214+
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
215+
}
216+
else if (attr != NULL) {
214217
Py_DECREF(attr);
215218
*other_op = Py_NewRef(other);
216219
return 0;
217220
}
218-
else if (PyErr_Occurred()) {
219-
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
220-
}
221221

222222
/*
223223
* Now check `other`. We want to know whether it is an object scalar

0 commit comments

Comments
 (0)