Description
Bug Report
Mypy raises false-positive errors if you pass an object that only has a __gt__
method to max()
.
To Reproduce
Consider the following class definitions:
class OnlyKnowsDunderLT:
def __init__(self, n):
self.n = n
def __lt__(self, other):
return self.n < other.n
def __repr__(self):
return f'OnlyKnowsDunderLT(n={self.n})'
class OnlyKnowsDunderGT:
def __init__(self, n):
self.n = n
def __gt__(self, other):
return self.n > other.n
def __repr__(self):
return f'OnlyKnowsDunderGT(n={self.n})'
Here's how they behave with max() at runtime in Python 3.10:
>>> max(OnlyKnowsDunderLT(5), OnlyKnowsDunderLT(6))
OnlyKnowsDunderLT(n=6)
>>> max(OnlyKnowsDunderGT(3), OnlyKnowsDunderGT(8))
OnlyKnowsDunderGT(n=8)
>>> max(OnlyKnowsDunderLT(3), OnlyKnowsDunderGT(9))
OnlyKnowsDunderGT(n=9)
Expected Behavior
Mypy should raise no errors for code such as this, as it works fine at runtime and is perfectly type-safe.
Actual Behavior
Mypy raises errors for the following snippet of code, a typed equivalent to the untyped code above:
from typing import Protocol
class HasN(Protocol):
n: int
class OnlyKnowsDunderLT:
def __init__(self, n: int) -> None:
self.n = n
def __lt__(self, other: HasN) -> bool:
return self.n < other.n
def __repr__(self) -> str:
return f'OnlyKnowsDunderLT(n={self.n})'
class OnlyKnowsDunderGT:
def __init__(self, n: int) -> None:
self.n = n
def __gt__(self, other: HasN) -> bool:
return self.n > other.n
def __repr__(self) -> str:
return f'OnlyKnowsDunderGT(n={self.n})'
max(OnlyKnowsDunderLT(5), OnlyKnowsDunderLT(6))
max(OnlyKnowsDunderGT(3), OnlyKnowsDunderGT(8)) # error: No overload variant of "max" matches argument types "OnlyKnowsDunderGT", "OnlyKnowsDunderGT" [call-overload]
max(OnlyKnowsDunderLT(3), OnlyKnowsDunderGT(9)) # error: No overload variant of "max" matches argument types "OnlyKnowsDunderLT", "OnlyKnowsDunderGT" [call-overload]
Over at typeshed, the stub for max()
has just been changed. max()
is now annotated as requiring arguments to have a __gt__
method rather than a __lt__
method, as this more accurately reflects the C code for max()
. However, the only effect of this change is to "flip" the errors that mypy reports: it will now error out on the class that only has a __lt__
method, instead of the class that only has a __gt__
method.
One potential solution to this problem could be to further revise the typeshed stub. However, I have been advised by @srittau that this would be a suboptimal solution, as __gt__
/__lt__
probably need to be special-cased by mypy and other type-checkers to some extent.
Your Environment
- Mypy version used: 0.910
- Mypy command-line flags:
--strict
,--show-error-codes
(the same errors occur without the--strict
flag) - Mypy configuration options from
mypy.ini
(and other config files): None - Python version used: 3.10