diff options
author | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2018-06-28 23:22:09 -0400 |
---|---|---|
committer | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2018-07-26 09:57:18 -0400 |
commit | eba017b43009b195eeada4feea9f0cde07c052b7 (patch) | |
tree | 0d4a6ff06872dbc356d25f30546b9c0e8865ed1b /numpy | |
parent | 1e1f08bd317657d63ede46de260f98ac0adf9b92 (diff) | |
download | numpy-eba017b43009b195eeada4feea9f0cde07c052b7.tar.gz |
MAINT,DEP: Properly implement ndarray.__pos__
For regular arrays, we now give a deprecation warning if
np.positive cannot handle it; for ndarray subclasses that override
__array_ufunc__, we pass on the type error.
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/src/multiarray/number.c | 53 | ||||
-rw-r--r-- | numpy/core/src/private/ufunc_override.c | 4 | ||||
-rw-r--r-- | numpy/core/src/private/ufunc_override.h | 28 | ||||
-rw-r--r-- | numpy/core/tests/test_deprecations.py | 7 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 10 |
5 files changed, 93 insertions, 9 deletions
diff --git a/numpy/core/src/multiarray/number.c b/numpy/core/src/multiarray/number.c index 448d2d9c2..f71d39405 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -15,6 +15,7 @@ #include "temp_elide.h" #include "binop_override.h" +#include "ufunc_override.h" /************************************************************************* **************** Implement Number Protocol **************************** @@ -550,6 +551,50 @@ array_power(PyArrayObject *a1, PyObject *o2, PyObject *modulo) return value; } +static PyObject * +array_positive(PyArrayObject *m1) +{ + /* + * For backwards compatibility, where + just implied a copy, + * we cannot just call n_ops.positive. Instead, we do the following + * 1. Try n_ops.positive + * 2. If we get an exception, check whether __array_ufunc__ is + * overridden; if so, we live in the future and we allow the + * TypeError to be passed on. + * 3. If not, give a deprecation warning and return a copy. + */ + PyObject *value; + if (can_elide_temp_unary(m1)) { + value = PyArray_GenericInplaceUnaryFunction(m1, n_ops.positive); + } + else { + value = PyArray_GenericUnaryFunction(m1, n_ops.positive); + } + if (value == NULL) { + /* + * We first fetch the error, as it needs to be clear to check + * for the override. When the deprecation is removed, + * this whole stanza can be deleted. + */ + PyObject *exc, *val, *tb; + PyErr_Fetch(&exc, &val, &tb); + if (has_non_default_array_ufunc((PyObject *)m1)) { + PyErr_Restore(exc, val, tb); + return NULL; + } + /* 2018-06-28, 1.16.0 */ + if (DEPRECATE("Applying '+' to a non-numerical array is " + "ill-defined. Returning a copy, but in the future " + "this will error.") < 0) { + return NULL; + } + Py_XDECREF(exc); + Py_XDECREF(val); + Py_XDECREF(tb); + value = PyArray_Return((PyArrayObject *)PyArray_Copy(m1)); + } + return value; +} static PyObject * array_negative(PyArrayObject *m1) @@ -927,12 +972,6 @@ array_hex(PyArrayObject *v) #endif static PyObject * -_array_copy_nice(PyArrayObject *self) -{ - return PyArray_Return((PyArrayObject *) PyArray_Copy(self)); -} - -static PyObject * array_index(PyArrayObject *v) { if (!PyArray_ISINTEGER(v) || PyArray_NDIM(v) != 0) { @@ -955,7 +994,7 @@ NPY_NO_EXPORT PyNumberMethods array_as_number = { (binaryfunc)array_divmod, /*nb_divmod*/ (ternaryfunc)array_power, /*nb_power*/ (unaryfunc)array_negative, /*nb_neg*/ - (unaryfunc)_array_copy_nice, /*nb_pos*/ + (unaryfunc)array_positive, /*nb_pos*/ (unaryfunc)array_absolute, /*(unaryfunc)array_abs,*/ (inquiry)_array_nonzero, /*nb_nonzero*/ (unaryfunc)array_invert, /*nb_invert*/ diff --git a/numpy/core/src/private/ufunc_override.c b/numpy/core/src/private/ufunc_override.c index 69c3cc56c..33b54c665 100644 --- a/numpy/core/src/private/ufunc_override.c +++ b/numpy/core/src/private/ufunc_override.c @@ -22,7 +22,7 @@ * nor to the default __array_ufunc__ method, so instead we import locally. * TODO: Can this really not be done more smartly? */ -static PyObject * +NPY_NO_EXPORT PyObject * get_non_default_array_ufunc(PyObject *obj) { static PyObject *ndarray = NULL; @@ -61,7 +61,7 @@ get_non_default_array_ufunc(PyObject *obj) * Returns 1 if this is the case, 0 if not. */ -static int +NPY_NO_EXPORT int has_non_default_array_ufunc(PyObject * obj) { PyObject *method = get_non_default_array_ufunc(obj); diff --git a/numpy/core/src/private/ufunc_override.h b/numpy/core/src/private/ufunc_override.h index fd1ee2135..5b269d270 100644 --- a/numpy/core/src/private/ufunc_override.h +++ b/numpy/core/src/private/ufunc_override.h @@ -4,6 +4,34 @@ #include "npy_config.h" /* + * Check whether an object has __array_ufunc__ defined on its class and it + * is not the default, i.e., the object is not an ndarray, and its + * __array_ufunc__ is not the same as that of ndarray. + * + * Returns a new reference, the value of type(obj).__array_ufunc__ + * + * If the __array_ufunc__ matches that of ndarray, or does not exist, return + * NULL. + * + * Note that since this module is used with both multiarray and umath, we do + * not have access to PyArray_Type and therewith neither to PyArray_CheckExact + * nor to the default __array_ufunc__ method, so instead we import locally. + * TODO: Can this really not be done more smartly? + */ +NPY_NO_EXPORT PyObject * +get_non_default_array_ufunc(PyObject *obj); + +/* + * Check whether an object has __array_ufunc__ defined on its class and it + * is not the default, i.e., the object is not an ndarray, and its + * __array_ufunc__ is not the same as that of ndarray. + * + * Returns 1 if this is the case, 0 if not. + */ +NPY_NO_EXPORT int +has_non_default_array_ufunc(PyObject * obj); + +/* * Check whether a set of input and output args have a non-default * `__array_ufunc__` method. Returns the number of overrides, setting * corresponding objects in PyObject array with_override (if not NULL). diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 0e7e9f55a..5d66d963f 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -505,9 +505,16 @@ class TestGeneratorSum(_DeprecationTestCase): def test_generator_sum(self): self.assert_deprecated(np.sum, args=((i for i in range(5)),)) + class TestSctypeNA(_VisibleDeprecationTestCase): # 2018-06-24, 1.16 def test_sctypeNA(self): self.assert_deprecated(lambda: np.sctypeNA['?']) self.assert_deprecated(lambda: np.typeNA['?']) self.assert_deprecated(lambda: np.typeNA.get('?')) + + +class TestPositiveOnNonNumerical(_DeprecationTestCase): + # 2018-06-28, 1.16.0 + def test_positive_on_non_number(self): + self.assert_deprecated(operator.pos, args=(np.array('foo'),)) diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 3812a20d8..1511f5b6b 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3414,6 +3414,16 @@ class TestBinop(object): assert_equal(obj_arr ** -1, pow_for(-1, obj_arr)) assert_equal(obj_arr ** 2, pow_for(2, obj_arr)) + def test_pos_array_ufunc_override(self): + class A(np.ndarray): + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + return getattr(ufunc, method)(*[i.view(np.ndarray) for + i in inputs], **kwargs) + tst = np.array('foo').view(A) + with assert_raises(TypeError): + +tst + + class TestTemporaryElide(object): # elision is only triggered on relatively large arrays |