diff --git a/doc/source/whatsnew/v1.5.1.rst b/doc/source/whatsnew/v1.5.1.rst index 9d40d9118db32..3425659ec77d6 100644 --- a/doc/source/whatsnew/v1.5.1.rst +++ b/doc/source/whatsnew/v1.5.1.rst @@ -32,6 +32,7 @@ Bug fixes Other ~~~~~ +- Avoid showing deprecated signatures when introspecting functions with warnings about arguments becoming keyword-only (:issue:`48692`) - - diff --git a/pandas/tests/util/test_deprecate_nonkeyword_arguments.py b/pandas/tests/util/test_deprecate_nonkeyword_arguments.py index 346561e0ba7ff..f6501fa8315e4 100644 --- a/pandas/tests/util/test_deprecate_nonkeyword_arguments.py +++ b/pandas/tests/util/test_deprecate_nonkeyword_arguments.py @@ -2,6 +2,7 @@ Tests for the `deprecate_nonkeyword_arguments` decorator """ +import inspect import warnings from pandas.util._decorators import deprecate_nonkeyword_arguments @@ -16,6 +17,10 @@ def f(a, b=0, c=0, d=0): return a + b + c + d +def test_f_signature(): + assert str(inspect.signature(f)) == "(a, b=0, *, c=0, d=0)" + + def test_one_argument(): with tm.assert_produces_warning(None): assert f(19) == 19 @@ -65,6 +70,10 @@ def g(a, b=0, c=0, d=0): return a + b + c + d +def test_g_signature(): + assert str(inspect.signature(g)) == "(a, *, b=0, c=0, d=0)" + + def test_one_and_three_arguments_default_allowed_args(): with tm.assert_produces_warning(None): assert g(1, b=3, c=3, d=5) == 12 @@ -93,6 +102,10 @@ def h(a=0, b=0, c=0, d=0): return a + b + c + d +def test_h_signature(): + assert str(inspect.signature(h)) == "(*, a=0, b=0, c=0, d=0)" + + def test_all_keyword_arguments(): with tm.assert_produces_warning(None): assert h(a=1, b=2) == 3 @@ -116,12 +129,25 @@ def test_one_positional_argument_with_warning_message_analysis(): ) +@deprecate_nonkeyword_arguments(version="1.1") +def i(a=0, /, b=0, *, c=0, d=0): + return a + b + c + d + + +def test_i_signature(): + assert str(inspect.signature(i)) == "(*, a=0, b=0, c=0, d=0)" + + class Foo: @deprecate_nonkeyword_arguments(version=None, allowed_args=["self", "bar"]) def baz(self, bar=None, foobar=None): ... +def test_foo_signature(): + assert str(inspect.signature(Foo.baz)) == "(self, bar=None, *, foobar=None)" + + def test_class(): msg = ( r"In a future version of pandas all arguments of Foo\.baz " diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 781f0604b043c..e95025371a204 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -290,14 +290,29 @@ def deprecate_nonkeyword_arguments( """ def decorate(func): + old_sig = inspect.signature(func) + if allowed_args is not None: allow_args = allowed_args else: - spec = inspect.getfullargspec(func) + allow_args = [ + p.name + for p in old_sig.parameters.values() + if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + and p.default is p.empty + ] - # We must have some defaults if we are deprecating default-less - assert spec.defaults is not None # for mypy - allow_args = spec.args[: -len(spec.defaults)] + new_params = [ + p.replace(kind=p.KEYWORD_ONLY) + if ( + p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + and p.name not in allow_args + ) + else p + for p in old_sig.parameters.values() + ] + new_params.sort(key=lambda p: p.kind) + new_sig = old_sig.replace(parameters=new_params) num_allow_args = len(allow_args) msg = ( @@ -307,15 +322,17 @@ def decorate(func): @wraps(func) def wrapper(*args, **kwargs): - arguments = _format_argument_list(allow_args) if len(args) > num_allow_args: warnings.warn( - msg.format(arguments=arguments), + msg.format(arguments=_format_argument_list(allow_args)), FutureWarning, stacklevel=find_stack_level(inspect.currentframe()), ) return func(*args, **kwargs) + # error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no + # attribute "__signature__" + wrapper.__signature__ = new_sig # type: ignore[attr-defined] return wrapper return decorate