Skip to content

Commit f31c4a6

Browse files
committed
MAINT,BUG: Refactor __array__ and never-copy to move check later
Doing the check before calling `PyArray_FromArrayAttr` means we look up the attribute twice. It further fixes two bugs: 1. The reference counting was wrong. 2. In debug mode there was a failure for some deprecation warnings (probably due to error propagation)
1 parent 84951a6 commit f31c4a6

File tree

2 files changed

+50
-15
lines changed

2 files changed

+50
-15
lines changed

numpy/core/src/multiarray/ctors.c

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,13 +1349,7 @@ _array_from_array_like(PyObject *op,
13491349
* this should be changed!
13501350
*/
13511351
if (!writeable && tmp == Py_NotImplemented) {
1352-
PyObject* array_meth = PyArray_LookupSpecial_OnInstance(op, "__array__");
1353-
int has_get = array_meth && PyType_Check(op) && PyObject_HasAttrString(array_meth, "__get__");
1354-
if (array_meth != NULL && !has_get && allow_copy) {
1355-
PyErr_SetString(PyExc_ValueError, "Calling __array__ in never copy mode is not allowed.");
1356-
return NULL;
1357-
}
1358-
tmp = PyArray_FromArrayAttr(op, requested_dtype, context);
1352+
tmp = PyArray_FromArrayAttr_int(op, requested_dtype, allow_copy);
13591353
if (tmp == NULL) {
13601354
return NULL;
13611355
}
@@ -2463,18 +2457,30 @@ PyArray_FromInterface(PyObject *origin)
24632457
return NULL;
24642458
}
24652459

2466-
/*NUMPY_API
2460+
2461+
/**
2462+
* Check for an __array__ attribute and call it when it exists.
2463+
*
2464+
* .. warning:
2465+
* If returned, `NotImplemented` is borrowed and must not be Decref'd
2466+
*
2467+
* @param op The Python object to convert to an array.
2468+
* @param descr The desired `arr.dtype`, passed into the `__array__` call,
2469+
* as information but is not checked/enforced!
2470+
* @param never_copy Indicator that a copy is not allowed.
2471+
* NOTE: Currently, this means an error is raised instead of calling
2472+
* `op.__array__()`. In the future we could call for example call
2473+
* `op.__array__(never_copy=True)` instead.
2474+
* @returns NotImplemented if `__array__` is not defined or a NumPy array
2475+
* (or subclass). On error, return NULL.
24672476
*/
24682477
NPY_NO_EXPORT PyObject *
2469-
PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
2478+
PyArray_FromArrayAttr_int(
2479+
PyObject *op, PyArray_Descr *descr, int never_copy)
24702480
{
24712481
PyObject *new;
24722482
PyObject *array_meth;
24732483

2474-
if (context != NULL) {
2475-
PyErr_SetString(PyExc_RuntimeError, "'context' must be NULL");
2476-
return NULL;
2477-
}
24782484
array_meth = PyArray_LookupSpecial_OnInstance(op, "__array__");
24792485
if (array_meth == NULL) {
24802486
if (PyErr_Occurred()) {
@@ -2490,6 +2496,16 @@ PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
24902496
}
24912497
return Py_NotImplemented;
24922498
}
2499+
if (never_copy) {
2500+
/* Currently, we must always assume that `__array__` returns a copy */
2501+
PyErr_SetString(PyExc_ValueError,
2502+
"Unable to avoid copy while converting from an object "
2503+
"implementing the `__array__` protocol. NumPy cannot ensure "
2504+
"that no copy will be made.");
2505+
Py_DECREF(array_meth);
2506+
return NULL;
2507+
}
2508+
24932509
if (PyType_Check(op) && PyObject_HasAttrString(array_meth, "__get__")) {
24942510
/*
24952511
* If the input is a class `array_meth` may be a property-like object.
@@ -2500,11 +2516,11 @@ PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
25002516
Py_DECREF(array_meth);
25012517
return Py_NotImplemented;
25022518
}
2503-
if (typecode == NULL) {
2519+
if (descr == NULL) {
25042520
new = PyObject_CallFunction(array_meth, NULL);
25052521
}
25062522
else {
2507-
new = PyObject_CallFunction(array_meth, "O", typecode);
2523+
new = PyObject_CallFunction(array_meth, "O", descr);
25082524
}
25092525
Py_DECREF(array_meth);
25102526
if (new == NULL) {
@@ -2520,6 +2536,21 @@ PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
25202536
return new;
25212537
}
25222538

2539+
2540+
/*NUMPY_API
2541+
*/
2542+
NPY_NO_EXPORT PyObject *
2543+
PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
2544+
{
2545+
if (context != NULL) {
2546+
PyErr_SetString(PyExc_RuntimeError, "'context' must be NULL");
2547+
return NULL;
2548+
}
2549+
2550+
return PyArray_FromArrayAttr_int(op, typecode, 0);
2551+
}
2552+
2553+
25232554
/*NUMPY_API
25242555
* new reference -- accepts NULL for mintype
25252556
*/

numpy/core/src/multiarray/ctors.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ PyArray_FromStructInterface(PyObject *input);
5252
NPY_NO_EXPORT PyObject *
5353
PyArray_FromInterface(PyObject *input);
5454

55+
NPY_NO_EXPORT PyObject *
56+
PyArray_FromArrayAttr_int(
57+
PyObject *op, PyArray_Descr *descr, int never_copy);
58+
5559
NPY_NO_EXPORT PyObject *
5660
PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode,
5761
PyObject *context);

0 commit comments

Comments
 (0)