summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2020-07-24 11:40:24 -0500
committerSebastian Berg <sebastian@sipsolutions.net>2020-07-24 11:40:24 -0500
commitd45b16dae51f9e8fa9b8296677e7a186b5efbc4a (patch)
treedaa6adc8ab52468a9a2dfee2f4f37c710b4b6ff9
parent2d12d0c2f7ac7b5e79d95b958b4c75871029b9a6 (diff)
downloadnumpy-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.c33
-rw-r--r--numpy/core/tests/test_array_coercion.py27
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