diff --git a/doc/release/1.7.0-notes.rst b/doc/release/1.7.0-notes.rst index 78e75884ca77..2346925b40d3 100644 --- a/doc/release/1.7.0-notes.rst +++ b/doc/release/1.7.0-notes.rst @@ -33,10 +33,14 @@ np.diagonal, numpy 1.7 produces a FutureWarning if it detects that you may be attemping to write to such an array. See the documentation for array indexing for details. -The default casting rule for UFunc out= parameters has been changed from -'unsafe' to 'same_kind'. Most usages which violate the 'same_kind' -rule are likely bugs, so this change may expose previously undetected -errors in projects that depend on NumPy. +In a future version of numpy, the default casting rule for UFunc out= +parameters will be changed from 'unsafe' to 'same_kind'. (This also +applies to in-place operations like a += b, which is equivalent to +np.add(a, b, out=a).) Most usages which violate the 'same_kind' rule +are likely bugs, so this change may expose previously undetected +errors in projects that depend on NumPy. In this version of numpy, +such usages will continue to succeed, but will raise a +DeprecationWarning. Full-array boolean indexing has been optimized to use a different, optimized code path. This code path should produce the same results, diff --git a/doc/source/reference/ufuncs.rst b/doc/source/reference/ufuncs.rst index 295d52ef4673..afcb1302b32e 100644 --- a/doc/source/reference/ufuncs.rst +++ b/doc/source/reference/ufuncs.rst @@ -309,6 +309,12 @@ advanced usage and will not typically be used. 'equiv', 'safe', 'same_kind', or 'unsafe'. See :func:`can_cast` for explanations of the parameter values. + In a future version of numpy, this argument will default to + 'same_kind'. As part of this transition, starting in version 1.7, + ufuncs will produce a DeprecationWarning for calls which are + allowed under the 'unsafe' rules, but not under the 'same_kind' + rules. + *order* .. versionadded:: 1.6 diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py index c49c3c346fbf..cb598880be1b 100644 --- a/numpy/core/code_generators/numpy_api.py +++ b/numpy/core/code_generators/numpy_api.py @@ -14,10 +14,12 @@ multiarray_global_vars = { 'NPY_NUMUSERTYPES': 7, + 'NPY_DEFAULT_ASSIGN_CASTING': 292, } multiarray_global_vars_types = { 'NPY_NUMUSERTYPES': 'int', + 'NPY_DEFAULT_ASSIGN_CASTING': 'NPY_CASTING', } multiarray_scalar_bool_values = { diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 954303352f82..93d561c7c511 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -199,11 +199,14 @@ typedef enum { /* Allow safe casts or casts within the same kind */ NPY_SAME_KIND_CASTING=3, /* Allow any casts */ - NPY_UNSAFE_CASTING=4 -} NPY_CASTING; + NPY_UNSAFE_CASTING=4, -/* The default casting to use for typical assignment operations */ -#define NPY_DEFAULT_ASSIGN_CASTING NPY_SAME_KIND_CASTING + /* + * Temporary internal definition only, will be removed in upcoming + * release, see below + * */ + NPY_INTERNAL_UNSAFE_CASTING_BUT_WARN_UNLESS_SAME_KIND = 100, +} NPY_CASTING; typedef enum { NPY_CLIP=0, diff --git a/numpy/core/src/multiarray/array_assign.c b/numpy/core/src/multiarray/array_assign.c index 9c1685c16448..4f350388bf4a 100644 --- a/numpy/core/src/multiarray/array_assign.c +++ b/numpy/core/src/multiarray/array_assign.c @@ -73,6 +73,7 @@ broadcast_error: { PyUString_ConcatAndDel(&errmsg, build_shape_string(ndim, shape)); PyErr_SetObject(PyExc_ValueError, errmsg); + Py_DECREF(errmsg); return -1; } diff --git a/numpy/core/src/multiarray/array_assign_array.c b/numpy/core/src/multiarray/array_assign_array.c index 4f462cb0567a..7e7cc0c29820 100644 --- a/numpy/core/src/multiarray/array_assign_array.c +++ b/numpy/core/src/multiarray/array_assign_array.c @@ -284,6 +284,7 @@ PyArray_AssignArray(PyArrayObject *dst, PyArrayObject *src, PyUString_FromFormat(" according to the rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); goto fail; } diff --git a/numpy/core/src/multiarray/array_assign_scalar.c b/numpy/core/src/multiarray/array_assign_scalar.c index 57f8b9074dbc..2c115426427f 100644 --- a/numpy/core/src/multiarray/array_assign_scalar.c +++ b/numpy/core/src/multiarray/array_assign_scalar.c @@ -209,6 +209,7 @@ PyArray_AssignRawScalar(PyArrayObject *dst, PyUString_FromFormat(" according to the rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index 5ab8f92bc76b..7b8177c5cb12 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -13,6 +13,22 @@ #include "common.h" #include "buffer.h" +/* + * The casting to use for implicit assignment operations resulting from + * in-place operations (like +=) and out= arguments. (Notice that this + * variable is misnamed, but it's part of the public API so I'm not sure we + * can just change it. Maybe someone should try and see if anyone notices. + */ +/* + * In numpy 1.6 and earlier, this was NPY_UNSAFE_CASTING. In a future + * release, it will become NPY_SAME_KIND_CASTING. Right now, during the + * transitional period, we continue to follow the NPY_UNSAFE_CASTING rules (to + * avoid breaking people's code), but we also check for whether the cast would + * be allowed under the NPY_SAME_KIND_CASTING rules, and if not we issue a + * warning (that people's code will be broken in a future release.) + */ +NPY_NO_EXPORT NPY_CASTING NPY_DEFAULT_ASSIGN_CASTING = NPY_INTERNAL_UNSAFE_CASTING_BUT_WARN_UNLESS_SAME_KIND; + NPY_NO_EXPORT PyArray_Descr * _array_find_python_scalar_type(PyObject *op) diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index de7468c51e00..586b85b1e9be 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -503,12 +503,43 @@ type_num_unsigned_to_signed(int type_num) } } +/* + * NOTE: once the UNSAFE_CASTING -> SAME_KIND_CASTING transition is over, + * we should remove NPY_INTERNAL_UNSAFE_CASTING_BUT_WARN_UNLESS_SAME_KIND + * and PyArray_CanCastTypeTo_impl should be renamed back to + * PyArray_CanCastTypeTo. + */ +static npy_bool +PyArray_CanCastTypeTo_impl(PyArray_Descr *from, PyArray_Descr *to, + NPY_CASTING casting); + /*NUMPY_API * Returns true if data of type 'from' may be cast to data of type * 'to' according to the rule 'casting'. */ NPY_NO_EXPORT npy_bool PyArray_CanCastTypeTo(PyArray_Descr *from, PyArray_Descr *to, + NPY_CASTING casting) +{ + if (casting == NPY_INTERNAL_UNSAFE_CASTING_BUT_WARN_UNLESS_SAME_KIND) { + npy_bool unsafe_ok, same_kind_ok; + unsafe_ok = PyArray_CanCastTypeTo_impl(from, to, NPY_UNSAFE_CASTING); + same_kind_ok = PyArray_CanCastTypeTo_impl(from, to, + NPY_SAME_KIND_CASTING); + if (unsafe_ok && !same_kind_ok) { + DEPRECATE("Implicitly casting between incompatible kinds. In " + "a future numpy release, this will raise an error. " + "Use casting=\"unsafe\" if this is intentional."); + } + return unsafe_ok; + } + else { + return PyArray_CanCastTypeTo_impl(from, to, casting); + } +} + +static npy_bool +PyArray_CanCastTypeTo_impl(PyArray_Descr *from, PyArray_Descr *to, NPY_CASTING casting) { /* If unsafe casts are allowed */ diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index ae29add6e765..40c53564147f 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -1818,6 +1818,7 @@ PyArray_FromArray(PyArrayObject *arr, PyArray_Descr *newtype, int flags) PyUString_FromFormat(" according to the rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); Py_DECREF(newtype); return NULL; diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index 6f70fc2ffd28..b5d0c3d1f441 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -1489,6 +1489,7 @@ raise_if_datetime64_metadata_cast_error(char *object_type, PyUString_FromFormat(" according to the rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -1520,6 +1521,7 @@ raise_if_timedelta64_metadata_cast_error(char *object_type, PyUString_FromFormat(" according to the rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -1654,6 +1656,7 @@ incompatible_units: { PyUString_FromString(" because they have " "incompatible nonlinear base time units")); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } units_overflow: { @@ -1666,6 +1669,7 @@ units_overflow: { PyUString_FromString(" and ")); errmsg = append_metastr_to_string(meta2, 0, errmsg); PyErr_SetObject(PyExc_OverflowError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -1813,6 +1817,7 @@ convert_datetime_metadata_tuple_to_datetime_metadata(PyObject *tuple, "datetime metadata conversion, not "); PyUString_ConcatAndDel(&errmsg, PyObject_Repr(tuple)); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index 596e9b83712d..6bb1cded5222 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -2538,6 +2538,7 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) errmsg = PyUString_FromString("Invalid datetime dtype (metadata, c_metadata): "); PyUString_ConcatAndDel(&errmsg, PyObject_Repr(metadata)); PyErr_SetObject(PyExc_ValueError, errmsg); + Py_DECREF(errmsg); return NULL; } diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 5ad4f2fd68f4..79e9fe0a6ef1 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -858,6 +858,7 @@ array_astype(PyArrayObject *self, PyObject *args, PyObject *kwds) PyUString_FromFormat(" according to the rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); Py_DECREF(dtype); return NULL; } diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index 8bdcc2c20e86..cba94daa1a9e 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -337,6 +337,16 @@ PyArray_ConcatenateArrays(int narrays, PyArrayObject **arrays, int axis) if (axis < 0) { axis += ndim; } + + if (ndim == 1 & axis != 0) { + char msg[] = "axis != 0 for ndim == 1; this will raise an error in " + "future versions of numpy"; + if (DEPRECATE(msg) < 0) { + return NULL; + } + axis = 0; + } + if (axis < 0 || axis >= ndim) { PyErr_Format(PyExc_IndexError, "axis %d out of bounds [0, %d)", orig_axis, ndim); diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c index 91768fa867ad..cfbaea3217c0 100644 --- a/numpy/core/src/multiarray/nditer_constr.c +++ b/numpy/core/src/multiarray/nditer_constr.c @@ -1337,6 +1337,7 @@ npyiter_check_casting(int nop, PyArrayObject **op, PyUString_FromFormat(" according to the rule %s", npyiter_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return 0; } /* Check write (temp -> op) casting */ @@ -1359,6 +1360,7 @@ npyiter_check_casting(int nop, PyArrayObject **op, (int)iop, npyiter_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return 0; } @@ -1738,6 +1740,7 @@ broadcast_error: { } PyErr_SetObject(PyExc_ValueError, errmsg); + Py_DECREF(errmsg); } else { errmsg = PyUString_FromString("operands could not be broadcast " @@ -1804,6 +1807,7 @@ broadcast_error: { } PyErr_SetObject(PyExc_ValueError, errmsg); + Py_DECREF(errmsg); } return 0; @@ -1890,6 +1894,7 @@ operand_different_than_broadcast: { } PyErr_SetObject(PyExc_ValueError, errmsg); + Py_DECREF(errmsg); return 0; } diff --git a/numpy/core/src/multiarray/shape.c b/numpy/core/src/multiarray/shape.c index 06723263277a..684d427139a0 100644 --- a/numpy/core/src/multiarray/shape.c +++ b/numpy/core/src/multiarray/shape.c @@ -1186,4 +1186,7 @@ PyArray_RemoveAxesInPlace(PyArrayObject *arr, npy_bool *flags) /* The final number of dimensions */ fa->nd = idim_out; + + /* Update contiguous flags */ + PyArray_UpdateFlags(arr, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS); } diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index 372edc07ab9f..5b090e88ec76 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -77,6 +77,7 @@ PyUFunc_ValidateCasting(PyUFuncObject *ufunc, PyUString_FromFormat(" with casting rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } else if (operands[i] != NULL) { @@ -95,6 +96,7 @@ PyUFunc_ValidateCasting(PyUFuncObject *ufunc, PyUString_FromFormat(" with casting rule %s", npy_casting_to_string(casting))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -725,6 +727,7 @@ type_reso_error: { PyUString_ConcatAndDel(&errmsg, PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -895,6 +898,7 @@ type_reso_error: { PyUString_ConcatAndDel(&errmsg, PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -1038,6 +1042,7 @@ type_reso_error: { PyUString_ConcatAndDel(&errmsg, PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -1157,6 +1162,7 @@ type_reso_error: { PyUString_ConcatAndDel(&errmsg, PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } } @@ -1273,6 +1279,7 @@ PyUFunc_DefaultLegacyInnerLoopSelector(PyUFuncObject *ufunc, } } PyErr_SetObject(PyExc_TypeError, errmsg); + Py_DECREF(errmsg); return -1; } diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 53471b2c21f9..696bae622468 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -1528,6 +1528,22 @@ def test_array_scalar_contiguous(self): assert_(np.array(np.float32(1.0)).flags.c_contiguous) assert_(np.array(np.float32(1.0)).flags.f_contiguous) + def test_squeeze_contiguous(self): + """Similar to GitHub issue #387""" + a = np.zeros((1,2)).squeeze() + b = np.zeros((2,2,2), order='F')[:,:,::2].squeeze() + assert_(a.flags.c_contiguous) + assert_(a.flags.f_contiguous) + assert_(b.flags.f_contiguous) + + def test_reduce_contiguous(self): + """GitHub issue #387""" + a = np.add.reduce(np.zeros((2,1,2)), (0,1)) + b = np.add.reduce(np.zeros((2,1,2)), 1) + assert_(a.flags.c_contiguous) + assert_(a.flags.f_contiguous) + assert_(b.flags.c_contiguous) + def test_object_array_self_reference(self): # Object arrays with references to themselves can cause problems a = np.array(0, dtype=object) diff --git a/numpy/core/tests/test_shape_base.py b/numpy/core/tests/test_shape_base.py index 17a9cbbde900..f72ed727fcc3 100644 --- a/numpy/core/tests/test_shape_base.py +++ b/numpy/core/tests/test_shape_base.py @@ -1,6 +1,7 @@ +import warnings from numpy.testing import * from numpy.core import array, atleast_1d, atleast_2d, atleast_3d, vstack, \ - hstack, newaxis + hstack, newaxis, concatenate, arange class TestAtleast1d(TestCase): def test_0D_array(self): @@ -37,6 +38,7 @@ def test_r1array(self): assert_(atleast_1d(3.0).shape == (1,)) assert_(atleast_1d([[2,3],[4,5]]).shape == (2,2)) + class TestAtleast2d(TestCase): def test_0D_array(self): a = array(1); b = array(2); @@ -97,6 +99,7 @@ def test_3D_array(self): desired = [a,b] assert_array_equal(res,desired) + class TestHstack(TestCase): def test_0D_array(self): a = array(1); b = array(2); @@ -116,6 +119,7 @@ def test_2D_array(self): desired = array([[1,1],[2,2]]) assert_array_equal(res,desired) + class TestVstack(TestCase): def test_0D_array(self): a = array(1); b = array(2); @@ -141,5 +145,70 @@ def test_2D_array2(self): desired = array([[1,2],[1,2]]) assert_array_equal(res,desired) +def test_concatenate(): + # Test concatenate function + # No arrays raise ValueError + assert_raises(ValueError, concatenate, ()) + # Scalars cannot be concatenated + assert_raises(ValueError, concatenate, (0,)) + assert_raises(ValueError, concatenate, (array(0),)) + # One sequence returns unmodified (but as array) + r4 = list(range(4)) + assert_array_equal(concatenate((r4,)), r4) + # Any sequence + assert_array_equal(concatenate((tuple(r4),)), r4) + assert_array_equal(concatenate((array(r4),)), r4) + # 1D default concatenation + r3 = list(range(3)) + assert_array_equal(concatenate((r4, r3)), r4 + r3) + # Mixed sequence types + assert_array_equal(concatenate((tuple(r4), r3)), r4 + r3) + assert_array_equal(concatenate((array(r4), r3)), r4 + r3) + # Explicit axis specification + assert_array_equal(concatenate((r4, r3), 0), r4 + r3) + # Including negative + assert_array_equal(concatenate((r4, r3), -1), r4 + r3) + # 2D + a23 = array([[10, 11, 12], [13, 14, 15]]) + a13 = array([[0, 1, 2]]) + res = array([[10, 11, 12], [13, 14, 15], [0, 1, 2]]) + assert_array_equal(concatenate((a23, a13)), res) + assert_array_equal(concatenate((a23, a13), 0), res) + assert_array_equal(concatenate((a23.T, a13.T), 1), res.T) + assert_array_equal(concatenate((a23.T, a13.T), -1), res.T) + # Arrays much match shape + assert_raises(ValueError, concatenate, (a23.T, a13.T), 0) + # 3D + res = arange(2 * 3 * 7).reshape((2, 3, 7)) + a0 = res[..., :4] + a1 = res[..., 4:6] + a2 = res[..., 6:] + assert_array_equal(concatenate((a0, a1, a2), 2), res) + assert_array_equal(concatenate((a0, a1, a2), -1), res) + assert_array_equal(concatenate((a0.T, a1.T, a2.T), 0), res.T) + + +def test_concatenate_sloppy0(): + # Versions of numpy < 1.7.0 ignored axis argument value for 1D arrays. We + # allow this for now, but in due course we will raise an error + r4 = list(range(4)) + r3 = list(range(3)) + assert_array_equal(concatenate((r4, r3), 0), r4 + r3) + warnings.simplefilter('ignore', DeprecationWarning) + try: + assert_array_equal(concatenate((r4, r3), -10), r4 + r3) + assert_array_equal(concatenate((r4, r3), 10), r4 + r3) + finally: + warnings.filters.pop(0) + # Confurm DepractionWarning raised + warnings.simplefilter('always', DeprecationWarning) + warnings.simplefilter('error', DeprecationWarning) + try: + assert_raises(DeprecationWarning, concatenate, (r4, r3), 10) + finally: + warnings.filters.pop(0) + warnings.filters.pop(0) + + if __name__ == "__main__": run_module_suite() diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 53928129f84f..57fd66892938 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -742,5 +742,23 @@ def t(expect, func, n, m): uf.accumulate(np.zeros((30, 30)), axis=0) uf.accumulate(np.zeros((0, 0)), axis=0) + def test_safe_casting(self): + # In old versions of numpy, in-place operations used the 'unsafe' + # casting rules. In some future version, 'same_kind' will become the + # default. + a = np.array([1, 2, 3], dtype=int) + # Non-in-place addition is fine + assert_array_equal(assert_no_warnings(np.add, a, 1.1), + [2.1, 3.1, 4.1]) + assert_warns(DeprecationWarning, np.add, a, 1.1, out=a) + assert_array_equal(a, [2, 3, 4]) + def add_inplace(a, b): + a += b + assert_warns(DeprecationWarning, add_inplace, a, 1.1) + assert_array_equal(a, [3, 4, 5]) + # Make sure that explicitly overriding the warning is allowed: + assert_no_warnings(np.add, a, 1.1, out=a, casting="unsafe") + assert_array_equal(a, [4, 5, 6]) + if __name__ == "__main__": run_module_suite() diff --git a/numpy/testing/tests/test_utils.py b/numpy/testing/tests/test_utils.py index bff8b50ab5c0..23b2f8e7b228 100644 --- a/numpy/testing/tests/test_utils.py +++ b/numpy/testing/tests/test_utils.py @@ -317,11 +317,15 @@ class TestWarns(unittest.TestCase): def test_warn(self): def f(): warnings.warn("yo") + return 3 before_filters = sys.modules['warnings'].filters[:] - assert_warns(UserWarning, f) + assert_equal(assert_warns(UserWarning, f), 3) after_filters = sys.modules['warnings'].filters + assert_raises(AssertionError, assert_no_warnings, f) + assert_equal(assert_no_warnings(lambda x: x, 1), 1) + # Check that the warnings state is unchanged assert_equal(before_filters, after_filters, "assert_warns does not preserver warnings state") diff --git a/numpy/testing/utils.py b/numpy/testing/utils.py index ffce2eefc836..16ed0f803a66 100644 --- a/numpy/testing/utils.py +++ b/numpy/testing/utils.py @@ -16,7 +16,8 @@ 'decorate_methods', 'jiffies', 'memusage', 'print_assert_equal', 'raises', 'rand', 'rundocs', 'runstring', 'verbose', 'measure', 'assert_', 'assert_array_almost_equal_nulp', - 'assert_array_max_ulp', 'assert_warns', 'assert_allclose'] + 'assert_array_max_ulp', 'assert_warns', 'assert_no_warnings', + 'assert_allclose'] verbose = 0 @@ -1464,7 +1465,7 @@ def assert_warns(warning_class, func, *args, **kw): Returns ------- - None + The value returned by `func`. """ @@ -1474,7 +1475,7 @@ def assert_warns(warning_class, func, *args, **kw): l = ctx.__enter__() warnings.simplefilter('always') try: - func(*args, **kw) + result = func(*args, **kw) if not len(l) > 0: raise AssertionError("No warning raised when calling %s" % func.__name__) @@ -1483,3 +1484,36 @@ def assert_warns(warning_class, func, *args, **kw): "%s( is %s)" % (func.__name__, warning_class, l[0])) finally: ctx.__exit__() + return result + +def assert_no_warnings(func, *args, **kw): + """ + Fail if the given callable produces any warnings. + + Parameters + ---------- + func : callable + The callable to test. + \\*args : Arguments + Arguments passed to `func`. + \\*\\*kwargs : Kwargs + Keyword arguments passed to `func`. + + Returns + ------- + The value returned by `func`. + + """ + # XXX: once we may depend on python >= 2.6, this can be replaced by the + # warnings module context manager. + ctx = WarningManager(record=True) + l = ctx.__enter__() + warnings.simplefilter('always') + try: + result = func(*args, **kw) + if len(l) > 0: + raise AssertionError("Got warnings when calling %s: %s" + % (func.__name__, l)) + finally: + ctx.__exit__() + return result