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
python/typeshed#2323 exposed a problem with mypy's (lack of) handling of protocols in ... if ... else ... constructs. Please consider:
from typing import SupportsFloat
class Floaty:
def __float__(self): ...
class Supporter(SupportsFloat):
def __float__(self): ...
reveal_type(Floaty() if bool() else 0)
reveal_type(Supporter() if bool() else 0)
This will reveal the type of the first expression to be builtins.object, while the latter is typing.SupportsFloat. This means that mypy (tested with 0.620, Python 3.7.0) will reject, for example float(Floaty() if bool() else 0).
On further thought, I wonder why mypy tries to infer the base type for if ... else ... constructs, instead of using an Union like with or. This makes it reject other valid code like this:
There's been discussion about this. There are pros and cons for returning unions for conditional expressions. It's my day off and I don't recall where the larger discussion lives. :-(
We looked at changing if/else expressions to always have union types, but it caused a lot of issues with code that relied on the existing semantics. Instead, we only modified it to infer a union type if the type context is a union type.
Similarly, we could infer a union/protocol type when the type context is a protocol type. Example:
fromtyping_extensionsimportProtocolclassP(Protocol):
x: intclassA:
x: intclassB:
x: intdeff(p: P) ->None: passf(A() ifbool() elseB()) # Should probably be okayy: P=A() ifbool() elseB() # Should probably be okay
This change wouldn't directly fix the original issue, but it would at least provide a somewhat simple workaround:
fromtypingimportSupportsFloatclassFloaty:
def__float__(self): ...
x: SupportsFloat=reveal_type(Floaty() ifbool() else0) # Would be SupportsFloat
This would clearly still be a compromise, but marginally better than what we currently have. It would also be consistent with how type inference issues are generally resolved -- by adding explicit type annotations. The fundamental problem is that sometimes there are multiple reasonable types we could infer, and we usually try to pick one of the options that we expect to be usually (but not always) a good one.
This compromise seems reasonable, although I'd argue that a "correct" solution would be better. I understand that that means changes to existing code bases, but I feel that typing and mypy are still in semi-experimental states (the typing module is still provisional, for example). Decisions that are made now will affect every Python user (that opt to use type hints) for the next decades.
This particular case irks me not only because of the practical problem discussed in the referenced issue, but also because of the inconsistency with or, which is used in many similar situations:
I agree that this is something that would be nice to fix, but the core team doesn't have the bandwidth to deal with the fallout right now :-(
The mypy fix is actually trivial, but fixing our internal Dropbox codebases would be a lot of work. The situation would likely be similar for many other mypy users with large codebases.
Another alternative would be to allow the old behavior to be retained with a configuration option.
I encountered this issue and made a test case when figuring out whether my code was incorrectly hinted or mypy was rejecting it unnecessarily. Perhaps the test case will be useful in implementing or discussing the handling of conditional expressions.
What is interesting about this test case, is that for f mypy reports "Incompatible return value type", but for g it doesn't report any errors, while the two functions are equivalent in execution.
Activity
srittau commentedon Jul 26, 2018
On further thought, I wonder why mypy tries to infer the base type for
if ... else ...constructs, instead of using anUnionlike withor. This makes it reject other valid code like this:srittau commentedon Jul 26, 2018
Continuing my train of thoughts: Changing
if ... else ...to return anUnionwould make the following code not check:Although I'd argue that in this case an explicit annotation for
xis better style anyway.Strangely enough, the following code does type check:
while the following does not:
gvanrossum commentedon Jul 26, 2018
Thanks for opening this issue.
There's been discussion about this. There are pros and cons for returning unions for conditional expressions. It's my day off and I don't recall where the larger discussion lives. :-(
JukkaL commentedon Jul 30, 2018
We looked at changing if/else expressions to always have union types, but it caused a lot of issues with code that relied on the existing semantics. Instead, we only modified it to infer a union type if the type context is a union type.
Similarly, we could infer a union/protocol type when the type context is a protocol type. Example:
This change wouldn't directly fix the original issue, but it would at least provide a somewhat simple workaround:
This would clearly still be a compromise, but marginally better than what we currently have. It would also be consistent with how type inference issues are generally resolved -- by adding explicit type annotations. The fundamental problem is that sometimes there are multiple reasonable types we could infer, and we usually try to pick one of the options that we expect to be usually (but not always) a good one.
srittau commentedon Jul 30, 2018
This compromise seems reasonable, although I'd argue that a "correct" solution would be better. I understand that that means changes to existing code bases, but I feel that typing and mypy are still in semi-experimental states (the
typingmodule is still provisional, for example). Decisions that are made now will affect every Python user (that opt to use type hints) for the next decades.This particular case irks me not only because of the practical problem discussed in the referenced issue, but also because of the inconsistency with
or, which is used in many similar situations:JukkaL commentedon Jul 30, 2018
I agree that this is something that would be nice to fix, but the core team doesn't have the bandwidth to deal with the fallout right now :-(
The mypy fix is actually trivial, but fixing our internal Dropbox codebases would be a lot of work. The situation would likely be similar for many other mypy users with large codebases.
Another alternative would be to allow the old behavior to be retained with a configuration option.
mthuurne commentedon Dec 22, 2018
I encountered this issue and made a test case when figuring out whether my code was incorrectly hinted or mypy was rejecting it unnecessarily. Perhaps the test case will be useful in implementing or discussing the handling of conditional expressions.
What is interesting about this test case, is that for
fmypy reports "Incompatible return value type", but forgit doesn't report any errors, while the two functions are equivalent in execution.FFY00 commentedon Aug 14, 2020
I would be open to work on this issue if someone can give me some pointers 😊
hauntsaninja commentedon Jul 3, 2024
Fixed by #17427