-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
The Dilemma
# current annotation in typeshed
class object:
def __eq__(self, value: object, /) -> bool: ...This is too strict, because everything subclasses object and some classes may to wish to:
- Narrow the range of allowed objects.
- Use a return type other than
bool(e.g. numpy array element-wise comparison, symbolic computation, etc.)
On the other hand, many APIs (incorrectly) assume that a == b always return a bool, so changing the return type away from bool can lead to problems.
Solving the Dilemma
Satisfying (1) is easy enough by changing value: object to value: Any. (one could maybe explore using Never also)
Satisfying (2) can be achieved in several ways, specifically I tested: (a) -> object, (b) -> Any and (c) -> Any | bool
-
Option (a) leads to very bad primer result as per the dilemma explained above.
-
Option (b) triggers a lot of
no-any-returnwarnings withmypywhen a function does somethign likereturn a==b. -
Option (c) is essentially a cheat code that avoids the
no-any-returnwarnings from option (b)
In particular, for (c) the primer actually looks really good. We have ~50 fixed Unused "type: ignore" comment errors, and 16 suprious [override] errors eliminated, without any new errors.
References
- https://discuss.python.org/t/make-type-hints-for-eq-of-primitives-less-strict/34240
- https://discuss.python.org/t/method-eq-overrides-class-object-in-an-incompatible-manner-well-not-always/42878
- https://discuss.python.org/t/hash-eq-and-lsp/68138
Appendix: How python's a==b works internally
This cannot be properly annotated with the current type system, so all type checkers need to special case it.
def eq(left, right):
left_type = type(left)
right_type = type(right)
# if RHS is proper subtype of LHS, try RHS's equality method
if left_type != right_type and issubclass(right_type, left_type):
result = right.__eq__(left)
if result is not NotImplemented:
return result
# try LHS's equality method
result = left.__eq__(right)
if result is not NotImplemented:
return result
# try RHS's equality method
result = right.__eq__(left)
if result is not NotImplemented:
return result
return False # fallback if all methods return NotImplemented