-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
Bind greedily assigns kwargs to args, which can cause bugs when binding to decorated functions #102551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I made a PR for this issue and listed the basic ideas in #102820. I've confirmed that this will fix the examples given above. The PR is still a prototype, and I probably need a review for whether that's the correct direction. If we decided to do that, we need to fix the unittest library or the unittest tests - basically, for |
Thanks, but I'm not sure this is a correct change to make to
Note that the issue in this PR is only a concern in the case that you're using Overall, I'm not sure there should be a change made here. Maybe the docs could be a little more explicit about the fact that args and kwargs being "dynamically computed from arguments" implies they don't remember how they were bound. |
Yeah, there are workarounds, but not ones I can implement (ie this is hitting me from another library and I don't have control over the code). Backwards compatibility is a right pain for sure, but this all comes down in my mind to what is the intended or best behaviour of From the doco it stays that Given that description, the fact that the below two lines don't preserve the information passed in seems like an obvious flaw in the method. I explicitly bind a kwarg, and the method forcefully turns it into an arg and there is nothing I can do about it. bound_signature = inspect.signature(add).bind(1, 2, c=100)
print(bound_signature.args, bound_signature.kwargs)
# (1, 2, 100) {} Perhaps we should agree on whether or not this is ideal or expected behaviour from the method, and can then discuss fixes? From what I see theres the proposal @gaogaotiantian has submitted. Another implementation might be to check the |
I agree that the first thing is to figure out - what is the proper behavior of Let's forget about the unittest failure, and focus on the class itself. I can understand if the Checking Now let's circle back to the unittest, the documentation actually never uses this "feature/bug". >>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow') It does not check for # To me, this assertion pass would actually makes me feel confused
>>> mock.method.assert_called_with(1, 2, 3, 'wow') |
+1. If the functionality to extra
Ah right, I see what you mean. Checking default using the signature parameters means that any deliberate "Im doing this as an arg!" would now greedily become a kwarg. So same problem as before, just reversed. Completely agree that the second assert which would pass would not be expected |
Went back on this issue today and realized in PEP 362 it's clearly stated
An example was given as well: 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} Like it or not, this is the designed behavior thus not a bug. We should probably document it more clearly though. I'll close the PR. |
Yeah, it's hard to argue with the PEP even if I don't know why that decision was made. Ah well. |
Bug report
Several repositories (like Prefect) make use of deferred execution of functions. They utilise inspect.Signature to create a bound method, and turn a list of parameters into args and kwargs to be passed in
*args, **kwargs
.When it works fine, it looks like this:
And this prints out:
Notice that the
100
has moved from a kwarg into an arg, but that's fine, it will still run.Things get complicated when decorators are introduced, and it seems base python has no method of getting args and kwargs that will work with both the wrapped and unwrapped signature.
The below code has two methods, each decorated, and both of them fail the bound execution.
And fails with:
They fail because of that default behaviour of treating kwargs as args.
In terms of expected behaviour, I would have assumed that a signature on a wrapped method, when resolving to args and kwargs, would be able to assign parameters between args and kwargs reliably.
Potential Solution
I notice that in
inspect.py -> BoundArguments -> args/kwargs properties
that some logic might be modified here.Instead of making all
_POSITIONAL_OR_KEYWORD
parameters args, I feel the language would be more correct to assign them based upon whether or not theparameter.default is Parameter.empty
. This may fix the issue and stop greedy conversion to arguments.Your environment
Linked PRs
The text was updated successfully, but these errors were encountered: