-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Generic function can't make function call with mapping unpacking #11583
Comments
Simplified sample: def f(b: bool = True, **kwargs: str) -> None:
pass
f(**{'x': 'x'}) This works in runtime, but |
@sobolevn Wow, thanks for simplifying what I thought was a MWE! And double thanks for helping to fix it! |
Related #1969 I am confused by this explicit test: https://github.com/python/mypy/blame/master/test-data/unit/check-kwargs.test#L478-L489 Maybe I am missing something 🤔 |
Yeah, that test also seems just wrong to me 😄 . |
Is there any progress on this issue? PR #11589 by @sobolevn was closed, and @JukkaL suggested making strict **kwargs type checking optional, which is really good idea. Recently pyright introduced strcit kwargs type checking (microsoft/pyright#5545), and they want to be consistent with other type-checkers, so they're doing it without option to disable it. Maybe someone have workarounds for this? my best guess is to cast kwargs to |
def f1(b: bool = True, **kwargs: str) -> None:
pass
f1(**{"x": "x"}) # Mypy: incompatible type
def f2(b: bool = True, *args: str) -> None:
pass
f2(*["a", "b", "c"]) # Mypy: incompatible type I recently changed pyright's behavior to match mypy's in this regard because it affects overload matching behaviors, and it's important for overload matching to be consistent across type checkers.
Edit: Actually, the dictionary unpack and iterable unpack cases are not equivalent. In the iterable unpack case, the runtime will always map the first result of the unpacked iterable to parameter @last-partizan, here are a couple of workarounds:
def f(b: bool = True, /, **kwargs: str) -> None:
pass
f(True, **{"x": "x"}) |
@hauntsaninja can we get your opinion on this? Current behaviour is a bug and should be fixed, or it works like it should? |
There's two things here. First, the Mypy error message is clearly a bug. It claims the first argument to the Second, while I can see how we got there, I can't think how the current Mypy behavior can be considered useful. It's not exactly a trivial fix:
If you specify the kwargs dictionary literal as a from typing import TypedDict, NotRequired
def f(b: bool = True, **kwargs: str) -> None:
pass
class FArgs(TypedDict, total=False):
b: bool
x: str
some_kwargs: FArgs = {'x': 'x'}
f(**some_kwargs) So solely on the fact that the variable is later used in dictionary unpacking on a signature, we would have to have Mypy infer I don't think it's worth only fixing the direct "dict literal into double star on a function call" as it's pretty artificial. But as soon as we name the dictionary and declare it somewhere else, it's easier to see that inferring its type based on the later double-star unpacking isn't an obvious proposition. What did pyright do before it was made to behave like Mypy? |
It's nothing as complex as what you're suggesting above. In the general case, you can't know whether a In most cases, mypy opts for eliminating false positives at the expense of potential false negatives. I think that's a good philosophy in general. For example, neither mypy nor pyright produce an error here even though a runtime error will result. def f(*, a: str, **kwargs: str) -> None:
pass
f(**{"x": "x"}) # Runtime error: missing keyword argument 'a'
f(a="", **{"a": "x"}) # Runtime error: multiple values for keyword argument 'a' So I think there's a good argument to be made that mypy is being inconsistent in opting for the more conservative approach and preferring potential false positives over potential false negatives. |
I agree with you in general, but in the example on this issue |
What do you think about making "immediately unpacked dict", a special case, and to infere it as In my case, it's syntax sugar for unittest.mock.patch. from unittest.mock import patch
# Instead of this
m = patch("requests.post")
m.configure_mock(**{"return_value.json.return_value": 1})
# I can write this:
m = patch("requests.post", **{"return_value.json.return_value": 1}) There is no alternative to this syntax, i cannot use keyword arguments, because |
Yes, we can do some special analysis for both cases:
|
gives
The text was updated successfully, but these errors were encountered: