Skip to content

Commit

Permalink
Merge pull request numpy#24978 from mdhaber/gh24931
Browse files Browse the repository at this point in the history
MAINT: testing: rename parameters x/y to actual/desired
  • Loading branch information
ngoldbaum authored Nov 29, 2023
2 parents 7f8dc13 + 93139e9 commit b2e0648
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 19 deletions.
5 changes: 5 additions & 0 deletions doc/release/upcoming_changes/24978.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Use of keyword arguments ``x`` and ``y`` with functions
`numpy.testing.assert_array_equal` and
`numpy.testing.assert_array_almost_equal`
has been deprecated. Pass the first two arguments as positional arguments,
instead.
58 changes: 58 additions & 0 deletions numpy/_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
in ``numpy._core``.
"""

import functools
import warnings
from ._convertions import asunicode, asbytes


Expand All @@ -27,3 +29,59 @@ def decorator(func):
func.__module__ = module
return func
return decorator


def _rename_parameter(old_names, new_names, dep_version=None):
"""
Generate decorator for backward-compatible keyword renaming.
Apply the decorator generated by `_rename_parameter` to functions with a
renamed parameter to maintain backward-compatibility.
After decoration, the function behaves as follows:
If only the new parameter is passed into the function, behave as usual.
If only the old parameter is passed into the function (as a keyword), raise
a DeprecationWarning if `dep_version` is provided, and behave as usual
otherwise.
If both old and new parameters are passed into the function, raise a
DeprecationWarning if `dep_version` is provided, and raise the appropriate
TypeError (function got multiple values for argument).
Parameters
----------
old_names : list of str
Old names of parameters
new_name : list of str
New names of parameters
dep_version : str, optional
Version of NumPy in which old parameter was deprecated in the format
'X.Y.Z'. If supplied, the deprecation message will indicate that
support for the old parameter will be removed in version 'X.Y+2.Z'
Notes
-----
Untested with functions that accept *args. Probably won't work as written.
"""
def decorator(fun):
@functools.wraps(fun)
def wrapper(*args, **kwargs):
for old_name, new_name in zip(old_names, new_names):
if old_name in kwargs:
if dep_version:
end_version = dep_version.split('.')
end_version[1] = str(int(end_version[1]) + 2)
end_version = '.'.join(end_version)
msg = (f"Use of keyword argument `{old_name}` is "
f"deprecated and replaced by `{new_name}`. "
f"Support for `{old_name}` will be removed "
f"in NumPy {end_version}.")
warnings.warn(msg, DeprecationWarning, stacklevel=2)
if new_name in kwargs:
msg = (f"{fun.__name__}() got multiple values for "
f"argument now known as `{new_name}`")
raise TypeError(msg)
kwargs[new_name] = kwargs.pop(old_name)
return fun(*args, **kwargs)
return wrapper
return decorator
45 changes: 26 additions & 19 deletions numpy/testing/_private/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
intp, float32, empty, arange, array_repr, ndarray, isnat, array)
from numpy import isfinite, isnan, isinf
import numpy.linalg._umath_linalg
from numpy._utils import _rename_parameter

from io import StringIO

Expand Down Expand Up @@ -874,7 +875,9 @@ def func_assert_same_pos(x, y, func=isnan, hasval='nan'):
raise ValueError(msg)


def assert_array_equal(x, y, err_msg='', verbose=True, *, strict=False):
@_rename_parameter(['x', 'y'], ['actual', 'desired'], dep_version='2.0.0')
def assert_array_equal(actual, desired, err_msg='', verbose=True, *,
strict=False):
"""
Raises an AssertionError if two array_like objects are not equal.
Expand All @@ -888,21 +891,22 @@ def assert_array_equal(x, y, err_msg='', verbose=True, *, strict=False):
The usual caution for verifying equality with floating point numbers is
advised.
.. note:: When either `x` or `y` is already an instance of `numpy.ndarray`
and `y` is not a ``dict``, the behavior of ``assert_equal(x, y)`` is
identical to the behavior of this function. Otherwise, this function
performs `np.asanyarray` on the inputs before comparison, whereas
`assert_equal` defines special comparison rules for common Python
types. For example, only `assert_equal` can be used to compare nested
Python lists. In new code, consider using only `assert_equal`,
explicitly converting either `x` or `y` to arrays if the behavior of
`assert_array_equal` is desired.
.. note:: When either `actual` or `desired` is already an instance of
`numpy.ndarray` and `desired` is not a ``dict``, the behavior of
``assert_equal(actual, desired)`` is identical to the behavior of this
function. Otherwise, this function performs `np.asanyarray` on the
inputs before comparison, whereas `assert_equal` defines special
comparison rules for common Python types. For example, only
`assert_equal` can be used to compare nested Python lists. In new code,
consider using only `assert_equal`, explicitly converting either
`actual` or `desired` to arrays if the behavior of `assert_array_equal`
is desired.
Parameters
----------
x : array_like
actual : array_like
The actual object to check.
y : array_like
desired : array_like
The desired, expected object.
err_msg : str, optional
The error message to be printed in case of failure.
Expand All @@ -928,8 +932,8 @@ def assert_array_equal(x, y, err_msg='', verbose=True, *, strict=False):
Notes
-----
When one of `x` and `y` is a scalar and the other is array_like, the
function checks that each element of the array_like object is equal to
When one of `actual` and `desired` is a scalar and the other is array_like,
the function checks that each element of the array_like object is equal to
the scalar. This behaviour can be disabled with the `strict` parameter.
Examples
Expand Down Expand Up @@ -996,13 +1000,15 @@ def assert_array_equal(x, y, err_msg='', verbose=True, *, strict=False):
DESIRED: array([2., 2., 2.], dtype=float32)
"""
__tracebackhide__ = True # Hide traceback for py.test
assert_array_compare(operator.__eq__, x, y, err_msg=err_msg,
assert_array_compare(operator.__eq__, actual, desired, err_msg=err_msg,
verbose=verbose, header='Arrays are not equal',
strict=strict)


@np._no_nep50_warning()
def assert_array_almost_equal(x, y, decimal=6, err_msg='', verbose=True):
@_rename_parameter(['x', 'y'], ['actual', 'desired'], dep_version='2.0.0')
def assert_array_almost_equal(actual, desired, decimal=6, err_msg='',
verbose=True):
"""
Raises an AssertionError if two objects are not equal up to desired
precision.
Expand All @@ -1025,9 +1031,9 @@ def assert_array_almost_equal(x, y, decimal=6, err_msg='', verbose=True):
Parameters
----------
x : array_like
actual : array_like
The actual object to check.
y : array_like
desired : array_like
The desired, expected object.
decimal : int, optional
Desired precision, default is 6.
Expand Down Expand Up @@ -1110,7 +1116,8 @@ def compare(x, y):

return z < 1.5 * 10.0**(-decimal)

assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose,
assert_array_compare(compare, actual, desired, err_msg=err_msg,
verbose=verbose,
header=('Arrays are not almost equal to %d decimals' % decimal),
precision=decimal)

Expand Down
29 changes: 29 additions & 0 deletions numpy/testing/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1878,3 +1878,32 @@ def __del__(self):
finally:
# make sure that we stop creating reference cycles
ReferenceCycleInDel.make_cycle = False


@pytest.mark.parametrize('assert_func', [assert_array_equal,
assert_array_almost_equal])
def test_xy_rename(assert_func):
# Test that keywords `x` and `y` have been renamed to `actual` and
# `desired`, respectively. These tests and use of `_rename_parameter`
# decorator can be removed before the release of NumPy 2.2.0.
assert_func(1, 1)
assert_func(actual=1, desired=1)

assert_message = "Arrays are not..."
with pytest.raises(AssertionError, match=assert_message):
assert_func(1, 2)
with pytest.raises(AssertionError, match=assert_message):
assert_func(actual=1, desired=2)

dep_message = 'Use of keyword argument...'
with pytest.warns(DeprecationWarning, match=dep_message):
assert_func(x=1, desired=1)
with pytest.warns(DeprecationWarning, match=dep_message):
assert_func(1, y=1)

type_message = '...got multiple values for argument'
# explicit linebreak to support Python 3.9
with pytest.warns(DeprecationWarning, match=dep_message), \
pytest.raises(TypeError, match=type_message):
assert_func(1, x=1)
assert_func(1, 2, y=2)

0 comments on commit b2e0648

Please sign in to comment.