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
Sure, that suppresses the error, but it seems like mypy (or possibly typeshed) has the wrong model of __hash__ if considers the type to be Callable[[object], int] rather than Optional[Callable[[object], int]] since hashability is optional.
Should I file this against typeshed instead?
Reacted by Alexander Huszagh, Stephen Lewis (Burrows) and Finite State Machine
No, this is not the wrong idea. Hashability is optional by convention, but as far as substitutability goes, once you define __hash__ once (and it is defined in object) subclasses must adhere to this protocol:
deff(x: object) ->Dict[object, int]: # return type is validreturn {x: 5} # must be valid
This is especially true when strict-optional exists, where it would otherwise force everyone to check for hashability before using hash which is of course unacceptable. For a purist, the fact that __hash__ is defined in object is unfortunate, but this is how the language works.
The fact that hashability is optional seems to work pretty well with you declaring not to subtype object by using __hash__ = None # type: ignore. It's true that this is not always tracked - assigning to object will lose type information, in this case, and mypy will not warn about it. Do you have any other issues with it? Or any suggestions as to what mypy should do with it? The mere complaint issued by mypy is technically correct and the workaround seems simple to me.
If you're happy that mypy is working as designed then you can close this. However, it seems like typing.Hashable can't possibly work in strict-optional mode, even if it is re-implemented in terms of typing_extensions.Protocol. For example:
#!/usr/bin/env python3
from typing_extensions import Protocol
class Hashable(Protocol):
def __hash__(object) -> int: ...
class Foo:
__hash__ = None # type: ignore
def print_hash(obj: Hashable):
print(hash(obj))
print_hash(3)
print_hash(Foo())
fails to identify that print_hash(Foo()) is malformed. On the other hand, removing the type: ignore and running without --strict-optional, it actually works as intended (hashable.py:14: error: Argument 1 to "print_hash" has incompatible type "Foo"; expected "Hashable").
So the issue is about making mypy not forget that the class is unhashable in the presence of type: ignore (or similar comment). That might be nice feature to add.
It will not be bullet-proof unless we'll make Foo incompatible with object, which is theoretically plausible but probably completely impractical. So you will still have:
x: object=Foo()
print_hash(x) # mypy cannot complain here
A class that does not declare a base implicitly inherits from object, and object provides a __hash__ method. But should that mean hashability is not optional in any case in a statically typed environment?
class A:
__hash__ = None
print(A.__bases__) # (<class 'object'>,)
$ mypy --version
mypy 0.782
$ mypy test.py
test.py:3: error: Incompatible types in assignment (expression has type "None", base class "object" defined the type as "Callable[[object], int]")
It isn't necessary to set __hash__ = None if a class defines __eq__ but not __hash__. I don't know if mypy understands that but when I found this error I realised that I could simply delete the __hash__ = None line with no effect. On the other hand if you have a class A that defines __eq__ and __hash__ and then a subclass B with __hash__ = None then that is a violation of substitutability since instances of A and B are not interchangeable in any context that needs hashing. If you don't define __eq__ then I'm not sure what the point of setting __hash__ = None is.
You can see from this that __hash__ is set to None but mypy doesn't complain in this case:
Activity
gvanrossum commentedon Nov 20, 2017
Is this a regression? Have you tried it with earlier versions? Or are you just mentioning 0.550 to indicate you're up to date?
bmerry commentedon Nov 20, 2017
I hadn't tried earlier versions - just indicating that I'm up-to-date.
I've now tested with 0.521, 0.530 and 0.540 and get the same behaviour - so it's not a regression, or at least not a recent one.
elazarg commentedon Nov 20, 2017
Doesn't
__hash__ = None # type: ignoredo the trick?bmerry commentedon Nov 20, 2017
Sure, that suppresses the error, but it seems like mypy (or possibly typeshed) has the wrong model of
__hash__if considers the type to beCallable[[object], int]rather thanOptional[Callable[[object], int]]since hashability is optional.Should I file this against typeshed instead?
elazarg commentedon Nov 20, 2017
No, this is not the wrong idea. Hashability is optional by convention, but as far as substitutability goes, once you define
__hash__once (and it is defined inobject) subclasses must adhere to this protocol:This is especially true when
strict-optionalexists, where it would otherwise force everyone to check for hashability before usinghashwhich is of course unacceptable. For a purist, the fact that__hash__is defined in object is unfortunate, but this is how the language works.The fact that hashability is optional seems to work pretty well with you declaring not to subtype
objectby using__hash__ = None # type: ignore. It's true that this is not always tracked - assigning toobjectwill lose type information, in this case, and mypy will not warn about it. Do you have any other issues with it? Or any suggestions as to what mypy should do with it? The mere complaint issued bymypyis technically correct and the workaround seems simple to me.bmerry commentedon Nov 20, 2017
If you're happy that mypy is working as designed then you can close this. However, it seems like
typing.Hashablecan't possibly work in strict-optional mode, even if it is re-implemented in terms oftyping_extensions.Protocol. For example:fails to identify that
print_hash(Foo())is malformed. On the other hand, removing thetype: ignoreand running without--strict-optional, it actually works as intended (hashable.py:14: error: Argument 1 to "print_hash" has incompatible type "Foo"; expected "Hashable").elazarg commentedon Nov 20, 2017
So the issue is about making mypy not forget that the class is unhashable in the presence of
type: ignore(or similar comment). That might be nice feature to add.It will not be bullet-proof unless we'll make
Fooincompatible withobject, which is theoretically plausible but probably completely impractical. So you will still have:ilevkivskyi commentedon Nov 20, 2017
Just FYI on current master
typing.Hashableis already a protocol (as well as several other similar things,typing.Iterableetc.)ilevkivskyi commentedon Jun 15, 2018
This is needed to allow python/typeshed#2221
NiklasRosenstein commentedon Aug 14, 2020
A class that does not declare a base implicitly inherits from
object, andobjectprovides a__hash__method. But should that mean hashability is not optional in any case in a statically typed environment?gvanrossum commentedon Sep 29, 2020
(The key request here is formulated in @elazarg's comment above: #4266 (comment))
oscarbenjamin commentedon Oct 3, 2021
It isn't necessary to set
__hash__ = Noneif a class defines__eq__but not__hash__. I don't know if mypy understands that but when I found this error I realised that I could simply delete the__hash__ = Noneline with no effect. On the other hand if you have a classAthat defines__eq__and__hash__and then a subclassBwith__hash__ = Nonethen that is a violation of substitutability since instances ofAandBare not interchangeable in any context that needs hashing. If you don't define__eq__then I'm not sure what the point of setting__hash__ = Noneis.You can see from this that
__hash__is set toNonebut mypy doesn't complain in this case:[-]`__hash__ = None` fails in strict-optional mode[/-][+]`__hash__ = None` generates an error[/+]Fix `__hash__` type annotation
Fix `__hash__` type annotation
srittau commentedon May 12, 2026
For reference, the typing spec is currently silent on the issue of hashability. I've filed python/typing#2284 to track that.
As of mypy 2.1.0 it's also not possible to mark a sub-class as hashable again: