diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2020-07-24 11:40:24 -0500 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2020-07-24 11:40:24 -0500 |
commit | d45b16dae51f9e8fa9b8296677e7a186b5efbc4a (patch) | |
tree | daa6adc8ab52468a9a2dfee2f4f37c710b4b6ff9 | |
parent | 2d12d0c2f7ac7b5e79d95b958b4c75871029b9a6 (diff) | |
download | numpy-d45b16dae51f9e8fa9b8296677e7a186b5efbc4a.tar.gz |
BUG: Allow array-like types to be coerced as object array elements
This was previously allowed for nested cases, i.e.:
np.array([np.int64])
but not for the scalar case:
np.array(np.int64)
The solution is to align these two cases by always interpreting
these as not being array-likes (instead of invalid array-likes)
if the passed in object is a `type` object and the special
attribute has a `__get__` attribute (and is thus probable a
property or method).
The (arguably better) alterative to this is to move the special
attribute lookup to be on the type instead of the instance
(which is what python does). This will definitely require some
adjustments in our tests to use properties, but is probably
fine with respect to most actual code.
(The tests more commonly use this to quickly set up an
array-like, while it is a fairly strange pattern for typical code.)
Address parts of gh-16939
Closes gh-8877
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 33 | ||||
-rw-r--r-- | numpy/core/tests/test_array_coercion.py | 27 |
2 files changed, 58 insertions, 2 deletions
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index cb448756b..dc451685a 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -1748,6 +1748,15 @@ PyArray_FromStructInterface(PyObject *input) } } if (!NpyCapsule_Check(attr)) { + if (PyType_Check(input) && PyObject_HasAttrString(attr, "__get__")) { + /* + * If the input is a class `attr` should be a property-like object. + * This cannot be interpreted as an array, but is a valid. + * (Needed due to the lookup being on the instance rather than type) + */ + Py_DECREF(attr); + return Py_NotImplemented; + } goto fail; } inter = NpyCapsule_AsVoidPtr(attr); @@ -1844,8 +1853,8 @@ PyArray_FromInterface(PyObject *origin) npy_intp dims[NPY_MAXDIMS], strides[NPY_MAXDIMS]; int dataflags = NPY_ARRAY_BEHAVED; - iface = PyArray_LookupSpecial_OnInstance(origin, - "__array_interface__"); + iface = PyArray_LookupSpecial_OnInstance(origin, "__array_interface__"); + if (iface == NULL) { if (PyErr_Occurred()) { PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */ @@ -1853,6 +1862,16 @@ PyArray_FromInterface(PyObject *origin) return Py_NotImplemented; } if (!PyDict_Check(iface)) { + if (PyType_Check(origin) && PyObject_HasAttrString(iface, "__get__")) { + /* + * If the input is a class `iface` should be a property-like object. + * This cannot be interpreted as an array, but is a valid. + * (Needed due to the lookup being on the instance rather than type) + */ + Py_DECREF(iface); + return Py_NotImplemented; + } + Py_DECREF(iface); PyErr_SetString(PyExc_ValueError, "Invalid __array_interface__ value, must be a dict"); @@ -2119,6 +2138,16 @@ PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context) } return Py_NotImplemented; } + if (PyType_Check(op) && PyObject_HasAttrString(array_meth, "__get__")) { + /* + * If the input is a class `array_meth` may be a property-like object. + * This cannot be interpreted as an array (called), but is a valid. + * Trying `array_meth.__call__()` on this should not be useful. + * (Needed due to the lookup being on the instance rather than type) + */ + Py_DECREF(array_meth); + return Py_NotImplemented; + } if (typecode == NULL) { new = PyObject_CallFunction(array_meth, NULL); } diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py index 30019b253..ba46a09a3 100644 --- a/numpy/core/tests/test_array_coercion.py +++ b/numpy/core/tests/test_array_coercion.py @@ -570,3 +570,30 @@ class TestArrayLikes: with pytest.raises(ValueError): # The error type does not matter much here. np.array([obj]) + + def test_arraylike_classes(self): + # The classes of array-likes should generally be acceptable to be + # stored inside a numpy (object) array. This tests all of the + # special attributes (since all are checked during coercion). + arr = np.array(np.int64) + assert arr[()] is np.int64 + arr = np.array([np.int64]) + assert arr[0] is np.int64 + + # This also works for properties/unbound methods: + class ArrayLike: + @property + def __array_interface__(self): + pass + + @property + def __array__struct(self): + pass + + def __array__(self): + pass + + arr = np.array(ArrayLike) + assert arr[()] is ArrayLike + arr = np.array([ArrayLike]) + assert arr[0] is ArrayLike |