You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 Any before unpacking.
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.
If my assumption is incorrect and there is general agreement that this behavior is undesirable, then I'd recommend that it be changed for both dictionary unpacking and iterable unpacking for consistency.
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 b. So ignore what I said above about consistency. There is still a question about whether mypy's current behavior with dictionary unpacking is intended. I assumed it was, so I matched the behavior in pyright, but I've received bug reports from several pyright users complaining about the change. I'm willing to revert the change in pyright if there's agreement that mypy's behavior is not desirable and there are plans to change it accordingly.
First, the Mypy error message is clearly a bug. It claims the first argument to the f() call is the exploded dict expression but that's not true. The keyword arguments get exploded from the provided dict, and in effect, the first argument is actually never provided.
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:
the literal gets assigned the type dict[str, str] because maybe somewhere along the way we want to mutate it by adding more key-value pairs;
the signature is actually a TypedDict(total=False) with the "b" key being boolean and all other keys being strings. We cannot currently map this type exactly to what TypedDict allows us to express (see Support for default types in TypedDict #6131).
If you specify the kwargs dictionary literal as a TypedDict type, Mypy is happy with the result:
So solely on the fact that the variable is later used in dictionary unpacking on a signature, we would have to have Mypy infer {'x': 'x'} being an anonymously built TypedDict(total=False) type with b: bool and other keys being strings. This seems to go contrary to how inference works now, which is "infer early and report incompatible usage as errors".
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?
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 dict[str, str] will contain a given key. A type checker needs to decide whether it wants to be more conservative (potentially producing a false positive) or less conservative (potentially producing a false negative). Mypy has opted in this case for the former, and a false positive error results. It's easy to switch the assumption to the latter and assume that if a parameter with a default argument is present, it doesn't need to be supplied by an unpacked dict argument.
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.
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.
Activity
sobolevn commentedon Nov 21, 2021
Simplified sample:
This works in runtime, but
mypy
's output is not in sync. I will try to fix it!Thanks for the report!
NeilGirdhar commentedon Nov 21, 2021
@sobolevn Wow, thanks for simplifying what I thought was a MWE! And double thanks for helping to fix it!
sobolevn commentedon Nov 21, 2021
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 🤔
NeilGirdhar commentedon Nov 21, 2021
Yeah, that test also seems just wrong to me 😄 .
last-partizan commentedon Jul 24, 2023
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
Any
before unpacking.erictraut commentedon Jul 24, 2023
Mypy's behavior here with dictionary unpacking is consistent with its behavior with iterable unpacking, so I assume it's intended behavior.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.
If my assumption is incorrect and there is general agreement that this behavior is undesirable, then I'd recommend that it be changed for both dictionary unpacking and iterable unpacking for consistency.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
b
. So ignore what I said above about consistency. There is still a question about whether mypy's current behavior with dictionary unpacking is intended. I assumed it was, so I matched the behavior in pyright, but I've received bug reports from several pyright users complaining about the change. I'm willing to revert the change in pyright if there's agreement that mypy's behavior is not desirable and there are plans to change it accordingly.@last-partizan, here are a couple of workarounds:
last-partizan commentedon Jul 25, 2023
@hauntsaninja can we get your opinion on this?
Current behaviour is a bug and should be fixed, or it works like it should?
ambv commentedon Jul 25, 2023
There's two things here.
First, the Mypy error message is clearly a bug. It claims the first argument to the
f()
call is the exploded dict expression but that's not true. The keyword arguments get exploded from the provided dict, and in effect, the first argument is actually never provided.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:
dict[str, str]
because maybe somewhere along the way we want to mutate it by adding more key-value pairs;TypedDict(total=False)
with the "b" key being boolean and all other keys being strings. We cannot currently map this type exactly to whatTypedDict
allows us to express (see Support for default types in TypedDict #6131).If you specify the kwargs dictionary literal as a
TypedDict
type, Mypy is happy with the result:So solely on the fact that the variable is later used in dictionary unpacking on a signature, we would have to have Mypy infer
{'x': 'x'}
being an anonymously builtTypedDict(total=False)
type withb: bool
and other keys being strings. This seems to go contrary to how inference works now, which is "infer early and report incompatible usage as errors".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?
erictraut commentedon Jul 25, 2023
It's nothing as complex as what you're suggesting above. In the general case, you can't know whether a
dict[str, str]
will contain a given key. A type checker needs to decide whether it wants to be more conservative (potentially producing a false positive) or less conservative (potentially producing a false negative). Mypy has opted in this case for the former, and a false positive error results. It's easy to switch the assumption to the latter and assume that if a parameter with a default argument is present, it doesn't need to be supplied by an unpacked dict argument.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.
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.
7 remaining items