summaryrefslogtreecommitdiff
path: root/numpy/core
diff options
context:
space:
mode:
authorMarten van Kerkwijk <mhvk@astro.utoronto.ca>2018-06-28 23:22:09 -0400
committerMarten van Kerkwijk <mhvk@astro.utoronto.ca>2018-07-26 09:57:18 -0400
commiteba017b43009b195eeada4feea9f0cde07c052b7 (patch)
tree0d4a6ff06872dbc356d25f30546b9c0e8865ed1b /numpy/core
parent1e1f08bd317657d63ede46de260f98ac0adf9b92 (diff)
downloadnumpy-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/core')
-rw-r--r--numpy/core/src/multiarray/number.c53
-rw-r--r--numpy/core/src/private/ufunc_override.c4
-rw-r--r--numpy/core/src/private/ufunc_override.h28
-rw-r--r--numpy/core/tests/test_deprecations.py7
-rw-r--r--numpy/core/tests/test_multiarray.py10
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