1 #ifndef __GET_ATTR_STRING_H
2 #define __GET_ATTR_STRING_H
3 
4 static NPY_INLINE npy_bool
_is_basic_python_type(PyTypeObject * tp)5 _is_basic_python_type(PyTypeObject *tp)
6 {
7     return (
8         /* Basic number types */
9         tp == &PyBool_Type ||
10         tp == &PyLong_Type ||
11         tp == &PyFloat_Type ||
12         tp == &PyComplex_Type ||
13 
14         /* Basic sequence types */
15         tp == &PyList_Type ||
16         tp == &PyTuple_Type ||
17         tp == &PyDict_Type ||
18         tp == &PySet_Type ||
19         tp == &PyFrozenSet_Type ||
20         tp == &PyUnicode_Type ||
21         tp == &PyBytes_Type ||
22 
23         /* other builtins */
24         tp == &PySlice_Type ||
25         tp == Py_TYPE(Py_None) ||
26         tp == Py_TYPE(Py_Ellipsis) ||
27         tp == Py_TYPE(Py_NotImplemented) ||
28 
29         /* TODO: ndarray, but we can't see PyArray_Type here */
30 
31         /* sentinel to swallow trailing || */
32         NPY_FALSE
33     );
34 }
35 
36 /*
37  * Stripped down version of PyObject_GetAttrString(obj, name) that does not
38  * raise PyExc_AttributeError.
39  *
40  * This allows it to avoid creating then discarding exception objects when
41  * performing lookups on objects without any attributes.
42  *
43  * Returns attribute value on success, NULL without an exception set if
44  * there is no such attribute, and NULL with an exception on failure.
45  */
46 static NPY_INLINE PyObject *
maybe_get_attr(PyObject * obj,char const * name)47 maybe_get_attr(PyObject *obj, char const *name)
48 {
49     PyTypeObject *tp = Py_TYPE(obj);
50     PyObject *res = (PyObject *)NULL;
51 
52     /* Attribute referenced by (char *)name */
53     if (tp->tp_getattr != NULL) {
54         res = (*tp->tp_getattr)(obj, (char *)name);
55         if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
56             PyErr_Clear();
57         }
58     }
59     /* Attribute referenced by (PyObject *)name */
60     else if (tp->tp_getattro != NULL) {
61         PyObject *w = PyUnicode_InternFromString(name);
62         if (w == NULL) {
63             return (PyObject *)NULL;
64         }
65         res = (*tp->tp_getattro)(obj, w);
66         Py_DECREF(w);
67         if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
68             PyErr_Clear();
69         }
70     }
71     return res;
72 }
73 
74 /*
75  * Lookup a special method, following the python approach of looking up
76  * on the type object, rather than on the instance itself.
77  *
78  * Assumes that the special method is a numpy-specific one, so does not look
79  * at builtin types, nor does it look at a base ndarray.
80  *
81  * In future, could be made more like _Py_LookupSpecial
82  */
83 static NPY_INLINE PyObject *
PyArray_LookupSpecial(PyObject * obj,char const * name)84 PyArray_LookupSpecial(PyObject *obj, char const *name)
85 {
86     PyTypeObject *tp = Py_TYPE(obj);
87 
88     /* We do not need to check for special attributes on trivial types */
89     if (_is_basic_python_type(tp)) {
90         return NULL;
91     }
92     return maybe_get_attr((PyObject *)tp, name);
93 }
94 
95 /*
96  * PyArray_LookupSpecial_OnInstance:
97  *
98  * Implements incorrect special method lookup rules, that break the python
99  * convention, and looks on the instance, not the type.
100  *
101  * Kept for backwards compatibility. In future, we should deprecate this.
102  */
103 static NPY_INLINE PyObject *
PyArray_LookupSpecial_OnInstance(PyObject * obj,char const * name)104 PyArray_LookupSpecial_OnInstance(PyObject *obj, char const *name)
105 {
106     PyTypeObject *tp = Py_TYPE(obj);
107 
108     /* We do not need to check for special attributes on trivial types */
109     if (_is_basic_python_type(tp)) {
110         return NULL;
111     }
112 
113     return maybe_get_attr(obj, name);
114 }
115 
116 #endif
117