8000 BUG: `__array_ufunc__= None` -> TypeError by shoyer · Pull Request #9014 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

BUG: __array_ufunc__= None -> TypeError #9014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 30, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
ENH: disable ufuncs if any operand sets __array_ufunc__=None
  • Loading branch information
shoyer committed Apr 30, 2017
commit b0825b438237ee6eb7f1340e5615e036267f7a05
9 changes: 7 additions & 2 deletions numpy/core/src/multiarray/methods.c
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012 8000 ,7 @@ array_ufunc(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
PyObject *ufunc, *method_name, *normal_args, *ufunc_method;
PyObject *result = NULL;
int num_override_args;

if (PyTuple_Size(args) < 2) {
PyErr_SetString(PyExc_TypeError,
Expand All @@ -1023,7 +1024,11 @@ array_ufunc(PyArrayObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
/* ndarray cannot handle overrides itself */
if (PyUFunc_WithOverride(normal_args, kwds, NULL)) {
num_override_args = PyUFunc_WithOverride(normal_args, kwds, NULL);
if (num_override_args == -1) {
return NULL;
}
if (num_override_args) {
result = Py_NotImplemented;
Py_INCREF(Py_NotImplemented);
goto cleanup;
Expand Down Expand Up @@ -2527,7 +2532,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = {
/*
* While we could put these in `tp_sequence`, its' easier to define them
* in terms of PyObject* arguments.
*
*
* We must provide these for compatibility with code that calls them
* directly. They are already deprecated at a language level in python 2.7,
* but are removed outright in python 3.
Expand Down
33 changes: 28 additions & 5 deletions numpy/core/src/private/ufunc_override.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,28 @@ has_non_default_array_ufunc(PyObject *obj)
return non_default;
}

/*
* Check whether an object sets __array_ufunc__ = None. The __array_func__
* attribute must already be known to exist.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might note that the existence of the attribute must be checked before calling this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

static int
disables_array_ufunc(PyObject *obj)
{
PyObject *array_ufunc;
int disables;

array_ufunc = PyObject_GetAttrString(obj, "__array_ufunc__");
disables = (array_ufunc == Py_None);
Py_XDECREF(array_ufunc);
return disables;
}

/*
* Check whether a set of input and output args have a non-default
* `__array_ufunc__` method. Return the number of overrides, setting
* corresponding objects in PyObject array with_override (if not NULL)
* using borrowed references.
*
*
* returns -1 on failure.
*/
NPY_NO_EXPORT int
Expand All @@ -65,7 +81,7 @@ PyUFunc_WithOverride(PyObject *args, PyObject *kwds,
int nargs;
int nout_kwd = 0;
int out_kwd_is_tuple = 0;
int noa = 0; /* Number of overriding args.*/
int num_override_args = 0;

PyObject *obj;
PyObject *out_kwd_obj = NULL;
Expand Down Expand Up @@ -117,13 +133,20 @@ PyUFunc_WithOverride(PyObject *args, PyObject *kwds,
* any ndarray subclass instances that did not override __array_ufunc__.
*/
if (has_non_default_array_ufunc(obj)) {
if (disables_array_ufunc(obj)) {
PyErr_Format(PyExc_TypeError,
"operand '%.200s' does not support ufuncs "
"(__array_ufunc__=None)",
obj->ob_type->tp_name);
goto fail;
}
if (with_override != NULL) {
with_override[noa] = obj;
with_override[num_override_args] = obj;
}
++noa;
++num_override_args;
}
}
return noa;
return num_override_args;

fail:
return -1;
Expand Down
19 changes: 8 additions & 11 deletions numpy/core/src/umath/override.c
< 8000 td class="blob-num blob-num-deletion empty-cell">
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
int j;
int status;

int noa;
int num_override_args;
PyObject *with_override[NPY_MAXARGS];

PyObject *obj;
Expand All @@ -334,9 +334,12 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
/*
* Check inputs for overrides
*/
noa = PyUFunc_WithOverride(args, kwds, with_override);
num_override_args = PyUFunc_WithOverride(args, kwds, with_override);
if (num_override_args == -1) {
goto fail;
}
/* No overrides, bail out.*/
if (noa == 0) {
if (num_override_args == 0) {
*result = NULL;
return 0;
}
Expand Down Expand Up @@ -496,7 +499,7 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
*result = NULL;

/* Choose an overriding argument */
for (i = 0; i < noa; i++) {
for (i = 0; i < num_override_args; i++) {
obj = with_override[i];
if (obj == NULL) {
continue;
Expand All @@ -506,7 +509,7 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
override_obj = obj;

/* Check for sub-types to the right of obj. */
for (j = i + 1; j < noa; j++) {
for (j = i + 1; j < num_override_args; j++) {
other_obj = with_override[j];
if (other_obj != NULL &&
PyObject_Type(other_obj) != PyObject_Type(obj) &&
Expand Down Expand Up @@ -552,12 +555,6 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
goto fail;
}

/* If None, try next one (i.e., as if it returned NotImplemented) */
if (array_ufunc == Py_None) {
Py_DECREF(array_ufunc);
continue;
}

*result = PyObject_Call(array_ufunc, override_args, normal_kwds);
Py_DECREF(array_ufunc);

Expand Down
33 changes: 32 additions & 1 deletion numpy/core/tests/test_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -1901,7 +1901,8 @@ def __array_ufunc__(self, *a, **kwargs):
def test_ufunc_override_not_implemented(self):

class A(object):
__array_ufunc__ = None
def __array_ufunc__(self, *args, **kwargs):
return NotImplemented

msg = ("operand type(s) do not implement __array_ufunc__("
"<ufunc 'negative'>, '__call__', <*>): 'A'")
Expand All @@ -1914,6 +1915,36 @@ class A(object):
with assert_raises_regex(TypeError, fnmatch.translate(msg)):
np.add(A(), object(), out=1)

def test_ufunc_override_disabled(self):

class OptOut(object):
__array_ufunc__ = None

opt_out = OptOut()

# ufuncs always raise
msg = "operand 'OptOut' does not support ufuncs"
with assert_raises_regex(TypeError, msg):
np.add(opt_out, 1)
with assert_raises_regex(TypeError, msg):
np.add(1, opt_out)
with assert_raises_regex(TypeError, msg):
np.negative(opt_out)

# opt-outs still hold even when other arguments have pathological
# __array_ufunc__ implementations

class GreedyArray(object):
def __array_ufunc__(self, *args, **kwargs):
return self

greedy = GreedyArray()
assert_(np.negative(greedy) is greedy)
with assert_raises_regex(TypeError, msg):
np.add(greedy, opt_out)
with assert_raises_regex(TypeError, msg):
np.add(greedy, 1, out=opt_out)

def test_gufunc_override(self):
# gufunc are just ufunc instances, but follow a different path,
# so check __array_ufunc__ overrides them properly.
Expand Down
0