From afda7480fe97d96d42e7ed8e519a7fe01c326709 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Tue, 9 Apr 2019 23:43:34 -0700 Subject: [PATCH 1/3] ENH: implement __numpy_implementation__ attribute for NEP-18 --- numpy/core/overrides.py | 2 + .../src/multiarray/arrayfunction_override.c | 2 +- numpy/core/src/multiarray/multiarraymodule.c | 7 ++- numpy/core/src/multiarray/multiarraymodule.h | 2 +- numpy/core/tests/test_overrides.py | 59 +++++++++++++++++-- 5 files changed, 62 insertions(+), 10 deletions(-) diff --git a/numpy/core/overrides.py b/numpy/core/overrides.py index 9f91adc83a37..ee713320ccca 100644 --- a/numpy/core/overrides.py +++ b/numpy/core/overrides.py @@ -152,6 +152,8 @@ def public_api(*args, **kwargs): if module is not None: public_api.__module__ = module + public_api.__numpy_implementation__ = implementation + return public_api return decorator diff --git a/numpy/core/src/multiarray/arrayfunction_override.c b/numpy/core/src/multiarray/arrayfunction_override.c index e62b32ab2e34..9a9d180c55fa 100644 --- a/numpy/core/src/multiarray/arrayfunction_override.c +++ b/numpy/core/src/multiarray/arrayfunction_override.c @@ -173,7 +173,7 @@ array_function_method_impl(PyObject *func, PyObject *types, PyObject *args, } } - implementation = PyObject_GetAttr(func, npy_ma_str_wrapped); + implementation = PyObject_GetAttr(func, npy_ma_str_numpy_implementation); if (implementation == NULL) { return NULL; } diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index b84de3a8d8af..cf30549515fa 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -4493,7 +4493,7 @@ NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_array_prepare = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_array_wrap = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_array_finalize = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_ufunc = NULL; -NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_wrapped = NULL; +NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_numpy_implementation = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_order = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_copy = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_dtype = NULL; @@ -4509,7 +4509,8 @@ intern_strings(void) npy_ma_str_array_wrap = PyUString_InternFromString("__array_wrap__"); npy_ma_str_array_finalize = PyUString_InternFromString("__array_finalize__"); npy_ma_str_ufunc = PyUString_InternFromString("__array_ufunc__"); - npy_ma_str_wrapped = PyUString_InternFromString("__wrapped__"); + npy_ma_str_numpy_implementation = PyUString_InternFromString( + "__numpy_implementation__"); npy_ma_str_order = PyUString_InternFromString("order"); npy_ma_str_copy = PyUString_InternFromString("copy"); npy_ma_str_dtype = PyUString_InternFromString("dtype"); @@ -4519,7 +4520,7 @@ intern_strings(void) return npy_ma_str_array && npy_ma_str_array_prepare && npy_ma_str_array_wrap && npy_ma_str_array_finalize && - npy_ma_str_ufunc && npy_ma_str_wrapped && + npy_ma_str_ufunc && npy_ma_str_numpy_implementation && npy_ma_str_order && npy_ma_str_copy && npy_ma_str_dtype && npy_ma_str_ndmin && npy_ma_str_axis1 && npy_ma_str_axis2; } diff --git a/numpy/core/src/multiarray/multiarraymodule.h b/numpy/core/src/multiarray/multiarraymodule.h index 6d33c32956c6..67504805cb69 100644 --- a/numpy/core/src/multiarray/multiarraymodule.h +++ b/numpy/core/src/multiarray/multiarraymodule.h @@ -6,7 +6,7 @@ NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_array_prepare; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_array_wrap; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_array_finalize; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_ufunc; -NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_wrapped; +NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_numpy_implementation; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_order; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_copy; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_dtype; diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 6e9610fffb3b..7515d607bbe7 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -2,6 +2,7 @@ import inspect import sys +from unittest import mock import numpy as np from numpy.testing import ( @@ -10,7 +11,6 @@ _get_implementing_args, array_function_dispatch, verify_matching_signatures) from numpy.compat import pickle -import pytest def _return_not_implemented(self, *args, **kwargs): @@ -190,12 +190,18 @@ class OverrideSub(np.ndarray): result = np.concatenate((array, override_sub)) assert_equal(result, expected.view(OverrideSub)) + def test_numpy_implementation(self): + assert_(dispatched_one_arg.__numpy_implementation__ + is dispatched_one_arg.__wrapped__) + def test_no_wrapper(self): + # This shouldn't happen unless a user intentionally calls + # __array_function__ with invalid arguments, but check that we raise + # an appropriate error all the same. array = np.array(1) - func = dispatched_one_arg.__wrapped__ - with assert_raises_regex(AttributeError, '__wrapped__'): - array.__array_function__(func=func, - types=(np.ndarray,), + func = dispatched_one_arg.__numpy_implementation__ + with assert_raises_regex(AttributeError, '__numpy_implementation__'): + array.__array_function__(func=func, types=(np.ndarray,), args=(array,), kwargs={}) @@ -378,3 +384,46 @@ def _(array): return 'yes' assert_equal(np.sum(MyArray()), 'yes') + + def test_sum_implementation_on_list(self): + assert_equal(np.sum.__numpy_implementation__([1, 2, 3]), 6) + + def test_sum_on_mock_array(self): + + # We need a proxy for mocks because __array_function__ is only looked + # up in the class dict + class ArrayProxy: + def __init__(self, value): + self.value = value + def __array_function__(self, *args, **kwargs): + return self.value.__array_function__(*args, **kwargs) + def __array__(self, *args, **kwargs): + return self.value.__array__(*args, **kwargs) + + proxy = ArrayProxy(mock.Mock(spec=ArrayProxy)) + proxy.value.__array_function__.return_value = 1 + result = np.sum(proxy) + assert_equal(result, 1) + proxy.value.__array_function__.assert_called_once_with( + np.sum, (ArrayProxy,), (proxy,), {}) + proxy.value.__array__.assert_not_called() + + proxy = ArrayProxy(mock.Mock(spec=ArrayProxy)) + proxy.value.__array__.return_value = np.array(2) + result = np.sum.__numpy_implementation__(proxy) + assert_equal(result, 2) + proxy.value.__array_function__.assert_not_called() + proxy.value.__array__.assert_called() + + def test_sum_forwarding_implementation(self): + + class MyArray(object): + + def sum(self, axis, out): + return 'summed' + + def __array_function__(self, func, types, args, kwargs): + return func.__numpy_implementation__(*args, **kwargs) + + # note: the internal implementation of np.sum() calls the .sum() method + assert_equal(np.sum(MyArray()), 'summed') From 389c19daa67fadf3b073c8e95bd01b5993de4fc1 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Mon, 22 Apr 2019 22:44:44 -0700 Subject: [PATCH 2/3] MNT: fix failure on Python 3.5 --- numpy/core/tests/test_overrides.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 7515d607bbe7..b895608cc9a0 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -412,8 +412,11 @@ def __array__(self, *args, **kwargs): proxy.value.__array__.return_value = np.array(2) result = np.sum.__numpy_implementation__(proxy) assert_equal(result, 2) - proxy.value.__array_function__.assert_not_called() - proxy.value.__array__.assert_called() + # TODO: switch to proxy.value.__array__.assert_called() and + # proxy.value.__array_function__.assert_not_called() once we drop + # Python 3.5 support. + ((called_method_name, _, _),) = proxy.value.mock_calls + assert_equal(called_method_name, '__array__') def test_sum_forwarding_implementation(self): From 804f71bb42fef775a85a52366dc4bd1a22c130a8 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 10 May 2019 17:25:52 -0700 Subject: [PATCH 3/3] Rename to __skip_array_function__ --- numpy/core/overrides.py | 2 +- numpy/core/src/multiarray/arrayfunction_override.c | 2 +- numpy/core/src/multiarray/multiarraymodule.c | 8 ++++---- numpy/core/src/multiarray/multiarraymodule.h | 2 +- numpy/core/tests/test_overrides.py | 14 +++++++------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/numpy/core/overrides.py b/numpy/core/overrides.py index ee713320ccca..d4697b8a6b8b 100644 --- a/numpy/core/overrides.py +++ b/numpy/core/overrides.py @@ -152,7 +152,7 @@ def public_api(*args, **kwargs): if module is not None: public_api.__module__ = module - public_api.__numpy_implementation__ = implementation + public_api.__skip_array_function__ = implementation return public_api diff --git a/numpy/core/src/multiarray/arrayfunction_override.c b/numpy/core/src/multiarray/arrayfunction_override.c index 9a9d180c55fa..02078306cb12 100644 --- a/numpy/core/src/multiarray/arrayfunction_override.c +++ b/numpy/core/src/multiarray/arrayfunction_override.c @@ -173,7 +173,7 @@ array_function_method_impl(PyObject *func, PyObject *types, PyObject *args, } } - implementation = PyObject_GetAttr(func, npy_ma_str_numpy_implementation); + implementation = PyObject_GetAttr(func, npy_ma_str_skip_array_function); if (implementation == NULL) { return NULL; } diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index cf30549515fa..06e15225d25f 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -4493,7 +4493,7 @@ NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_array_prepare = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_array_wrap = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_array_finalize = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_ufunc = NULL; -NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_numpy_implementation = NULL; +NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_skip_array_function = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_order = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_copy = NULL; NPY_VISIBILITY_HIDDEN PyObject * npy_ma_str_dtype = NULL; @@ -4509,8 +4509,8 @@ intern_strings(void) npy_ma_str_array_wrap = PyUString_InternFromString("__array_wrap__"); npy_ma_str_array_finalize = PyUString_InternFromString("__array_finalize__"); npy_ma_str_ufunc = PyUString_InternFromString("__array_ufunc__"); - npy_ma_str_numpy_implementation = PyUString_InternFromString( - "__numpy_implementation__"); + npy_ma_str_skip_array_function = PyUString_InternFromString( + "__skip_array_function__"); npy_ma_str_order = PyUString_InternFromString("order"); npy_ma_str_copy = PyUString_InternFromString("copy"); npy_ma_str_dtype = PyUString_InternFromString("dtype"); @@ -4520,7 +4520,7 @@ intern_strings(void) return npy_ma_str_array && npy_ma_str_array_prepare && npy_ma_str_array_wrap && npy_ma_str_array_finalize && - npy_ma_str_ufunc && npy_ma_str_numpy_implementation && + npy_ma_str_ufunc && npy_ma_str_skip_array_function && npy_ma_str_order && npy_ma_str_copy && npy_ma_str_dtype && npy_ma_str_ndmin && npy_ma_str_axis1 && npy_ma_str_axis2; } diff --git a/numpy/core/src/multiarray/multiarraymodule.h b/numpy/core/src/multiarray/multiarraymodule.h index 67504805cb69..5cf082fbbc2e 100644 --- a/numpy/core/src/multiarray/multiarraymodule.h +++ b/numpy/core/src/multiarray/multiarraymodule.h @@ -6,7 +6,7 @@ NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_array_prepare; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_array_wrap; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_array_finalize; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_ufunc; -NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_numpy_implementation; +NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_skip_array_function; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_order; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_copy; NPY_VISIBILITY_HIDDEN extern PyObject * npy_ma_str_dtype; diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index b895608cc9a0..7f02399b248f 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -190,8 +190,8 @@ class OverrideSub(np.ndarray): result = np.concatenate((array, override_sub)) assert_equal(result, expected.view(OverrideSub)) - def test_numpy_implementation(self): - assert_(dispatched_one_arg.__numpy_implementation__ + def test_skip_array_function(self): + assert_(dispatched_one_arg.__skip_array_function__ is dispatched_one_arg.__wrapped__) def test_no_wrapper(self): @@ -199,8 +199,8 @@ def test_no_wrapper(self): # __array_function__ with invalid arguments, but check that we raise # an appropriate error all the same. array = np.array(1) - func = dispatched_one_arg.__numpy_implementation__ - with assert_raises_regex(AttributeError, '__numpy_implementation__'): + func = dispatched_one_arg.__skip_array_function__ + with assert_raises_regex(AttributeError, '__skip_array_function__'): array.__array_function__(func=func, types=(np.ndarray,), args=(array,), kwargs={}) @@ -386,7 +386,7 @@ def _(array): assert_equal(np.sum(MyArray()), 'yes') def test_sum_implementation_on_list(self): - assert_equal(np.sum.__numpy_implementation__([1, 2, 3]), 6) + assert_equal(np.sum.__skip_array_function__([1, 2, 3]), 6) def test_sum_on_mock_array(self): @@ -410,7 +410,7 @@ def __array__(self, *args, **kwargs): proxy = ArrayProxy(mock.Mock(spec=ArrayProxy)) proxy.value.__array__.return_value = np.array(2) - result = np.sum.__numpy_implementation__(proxy) + result = np.sum.__skip_array_function__(proxy) assert_equal(result, 2) # TODO: switch to proxy.value.__array__.assert_called() and # proxy.value.__array_function__.assert_not_called() once we drop @@ -426,7 +426,7 @@ def sum(self, axis, out): return 'summed' def __array_function__(self, func, types, args, kwargs): - return func.__numpy_implementation__(*args, **kwargs) + return func.__skip_array_function__(*args, **kwargs) # note: the internal implementation of np.sum() calls the .sum() method assert_equal(np.sum(MyArray()), 'summed')