python / cpython

The Python programming language
https://www.python.org/
Other
61.16k stars 29.52k forks source link

inspect.signature.BoundArguments "POSITIONAL_OR_KEYWORD" Arguments are always args #118577

Open Steffenju95 opened 2 months ago

Steffenju95 commented 2 months ago

Feature Request

Feature description:

from inspect import signature
def foo(bar, /, baz, *, bat):
    ...

ba = signature(foo).bind('bla', baz='bli', bat='blub')

In this case the argument baz get returned with ba.args, which returns ('bla', 'bli') I expected it in ba.kwargs, but that returns only {bat: 'blub'}.

Binding baz positional (bind('bla', 'bli', bat='blub')) works as expected. Here ba.args returns ('bla', 'bli') again and ba.kwargs {bat: 'blub'}.

Is this behavior wanted like this? In my perspective it makes more sense when the BoundArguments class returns POSITIONAL_OR_KEYWORD arguments as it got it (positional as args and keyword as kwargs) and not always as args. I think there happens a lost of Information .

Edit: At least it not cause errors, because in constellations, where it is necessary to be a keyword it is. So it also could argued against the information lost, that it is a build in feature of simplification of unnecessary keywords.

See the following example:

from inspect import signature
def foo(bar, /, new='blub', baz='bla', *, bat='bli'):
    ...

ba = signature(foo).bind('bla', baz='bli', bat='blub')

ba.args => ('bla',) ba.kwargs => {'baz': 'bli', 'bat': 'blub'}

CPython versions tested on:

3.12

Operating systems tested on:

Windows

Linked PRs

terryjreedy commented 2 months ago

Running your copy-pasted code in current 3.11.9, 3.12.3, and 3.13.0a6 on Windows, print(ba.args, ba.kwargs) returns ('bla', 'bli') {'bat': 'blub'}. Note that args is a tuple, not a dict, as one should expect. There must have been a fix since the 3.12 you have.

Steffenju95 commented 2 months ago

Running your copy-pasted code in current 3.11.9, 3.12.3, and 3.13.0a6 on Windows, print(ba.args, ba.kwargs) returns ('bla', 'bli') {'bat': 'blub'}. Note that args is a tuple, not a dict, as one should expect. There must have been a fix since the 3.12 you have.

Thank you for you fast reply. You are absolutely right, It's behaves like you said. I should next time check more carefully and copy paste my tested code.

But one point stays. I still think baz='bli' should be returned as kwargs, because otherwise there is a lost of information. I regarding to that I corrected my initial post.

I'm just not sure if it's still a bug report or more a feature request. I guess it's depends on, if the actual behavior is intended as it is.

hauntsaninja commented 2 months ago

See also #102551

Steffenju95 commented 2 months ago

See also #102551

Okay, so it's definitely intended as it is. (I missed that in the PEP 362)

~~I agree with this comment and think it would be good to change the actual behavior: https://github.com/python/cpython/issues/102551#issuecomment-1475438832~~

I also don't see so far an argument against it, beside the PEP defines it different and backward compatibility.

~~For the implementation there is not even a new variable needed as proposed before. You could just pass the args and kwargs argument in addition to argument and let the property methods assess it instead of extracting it from attributes and signature. So nothing would change, beside two more arguments in __init__ and to private attributes _args and _kwargs that replaced the code to extract it from attributes and signature.~~

But if there are good arguments against it, I think it would be a good idea to address this unintuitive behaviour beside the one sentence in the PEP also in the code doc and here: https://docs.python.org/3/library/inspect.html#inspect.BoundArguments.args

Edit: I realized in the meantime that beside the feature to reduce the args and kwargs to the minimum (See my original Post https://github.com/python/cpython/issues/118577#issue-2279113456), you can change the arguments dict of BoundArguments and it will calculate you the correct args and kwargs for the new binding. So changing this behaviour as suggested could cause problems. So I root for a better documentation of the unintuitive behaviour.

terryjreedy commented 2 months ago

Please suggest specific doc changes in a comment.

Steffenju95 commented 2 months ago

args A tuple of positional arguments values. Dynamically computed by applying the arguments attribute to the corresponding signature. Includes ambiguous arguments (See Note).

kwargs A dict of keyword arguments values. Dynamically computed by applying the arguments attribute to the corresponding signature. Excludes ambiguous arguments (See Note).

Note: The allocation in args and kwargs may not match the inserted args and kwargs in Signature.bind() or Signature.bind_partial(). This concerns argument of the kind POSITIONAL_OR_KEYWORD in cases where they can be passed ambiguous as args or kwargs. In cases of ambiguity the dynamically computation of args and kwargs always simplifies the given arguments as much as possible, by dropping keywords and saving them positional in args. For example:

def test(a=1, b=2, c=3):
    pass

sig = signature(test)
ba = sig.bind(a=10, c=13)

>>> ba.args
(10,)

>>> ba.kwargs:
{'c': 13}