8000 BUG: Allow array-like types to be coerced as object array elements · seberg/numpy@d45b16d · GitHub
[go: up one dir, main page]

Skip to content

Commit d45b16d

Browse files
committed
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 numpygh-16939 Closes numpygh-8877
1 parent 2d12d0c commit d45b16d

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

numpy/core/src/multiarray/ctors.c

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1748,6 +1748,15 @@ PyArray_FromStructInterface(PyObject *input)
17481748
}
17491749
}
17501750
if (!NpyCapsule_Check(attr)) {
1751+
if (PyType_Check(input) && PyObject_HasAttrString(attr, "__get__")) {
1752+
/*
1753+
* If the input is a class `attr` should be a property-like object.
1754+
* This cannot be interpreted as an array, but is a valid.
1755+
* (Needed due to the lookup being on the instance rather than type)
1756+
*/
1757+
Py_DECREF(attr);
1758+
return Py_NotImplemented;
1759+
}
17511760
goto fail;
17521761
}
17531762
inter = NpyCapsule_AsVoidPtr(attr);
@@ -1844,15 +1853,25 @@ PyArray_FromInterface(PyObject *origin)
18441853
npy_intp dims[NPY_MAXDIMS], strides[NPY_MAXDIMS];
18451854
int dataflags = NPY_ARRAY_BEHAVED;
18461855

1847-
iface = PyArray_LookupSpecial_OnInstance(origin,
1848-
"__array_interface__");
1856+
iface = PyArray_LookupSpecial_OnInstance(origin, "__array_interface__");
1857+
18491858
if (iface == NULL) {
18501859
if (PyErr_Occurred()) {
18511860
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
18521861
}
18531862
return Py_NotImplemented;
18541863
}
18551864
if (!PyDict_Check(iface)) {
1865+
if (PyType_Check(origin) && PyObject_HasAttrString(iface, "__get__")) {
1866+
/*
1867+
* If the input is a class `iface` should be a property-like object.
1868+
* This cannot be interpreted as an array, but is a valid.
1869+
* (Needed due to the lookup being on the instance rather than type)
1870+
*/
1871+
Py_DECREF(iface);
1872+
return Py_NotImplemented;
1873+
}
1874+
18561875
Py_DECREF(iface);
18571876
PyErr_SetString(PyExc_ValueError,
18581877
"Invalid __array_interface__ value, must be a dict");
@@ -2119,6 +2138,16 @@ PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
21192138
}
21202139
return Py_NotImplemented;
21212140
}
2141+
if (PyType_Check(op) && PyObject_HasAttrString(array_meth, "__get__")) {
2142+
/*
2143+
* If the input is a class `array_meth` may be a property-like object.
2144+
* This cannot be interpreted as an array (called), but is a valid.
2145+
* Trying `array_meth.__call__()` on this should not be useful.
2146+
* (Needed due to the lookup being on the instance rather than type)
2147+
*/
2148+
Py_DECREF(array_meth);
2149+
return Py_NotImplemented;
2150+
}
21222151
if (typecode == NULL) {
21232152
new = PyObject_CallFunction(array_meth, NULL);
21242153
}

numpy/core/tests/test_array_coercion.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,3 +570,30 @@ def __float__(self):
570570
with pytest.raises(ValueError):
571571
# The error type does not matter much here.
572572
np.array([obj])
573+
574+
def test_arraylike_classes(self):
575+
# The classes of array-likes should generally be acceptable to be
576+
# stored inside a numpy (object) array. This tests all of the
577+
# special attributes (since all are checked during coercion).
578+
arr = np.array(np.int64)
579+
assert arr[()] is np.int64
580+
arr = np.array([np.int64])
581+
assert arr[0] is np.int64
582+
583+
# This also works for properties/unbound methods:
584+
class ArrayLike:
585+
@property
586+
def __array_interface__(self):
587+
pass
588+
589+
@property
590+
def __array__struct(self):
591+
pass
592+
593+
def __array__(self):
594+
pass
595+
596+
arr = np.array(ArrayLike)
597+
assert arr[()] is ArrayLike
598+
arr = np.array([ArrayLike])
599+
assert arr[0] is ArrayLike

0 commit comments

Comments
 (0)
0