GeezerVison is showing github.com — Special-casing `__hash__` · Issue #2284 · python/typing
Special-casing __hash__ #2284DescriptionThe concept of "hashable" and "unhashable" classes has been a bit of the sore point in the past. typeshed uses __hash__: ClassVar[None] to mark classes as non-hashable, although that requires a # type: ignore[assignment] annotation. I think this is special-cased by at least some type checkers for marking classes as unhashable.
Except for some wordage in the dataclasses section, the typing spec is silent about hashability. Since I believe that this needs special-casing by type checkers, it should be added to the typing spec. A raw idea:
- Classes are hashable by default (due to
object.__hash__() being implemented and typeshed having a type corresponding type annotation.)
- To mark a class as non-hashable, use
__hash__: ClassVar[None]. We could alternatively use something simpler such as __hash__ = None. I believe we used to use that, but I don't know why it's changed.
- To mark a class as hashable, use
def __hash__(self, ...) -> ..., usually def __hash__(self) -> int: ....
- Sub-classes inherit their parent's hashability, unless overridden.
ActivityJelleZijlstra commented on May 12, 2026 There was a previous discussion about this at https://discuss.python.org/t/hash-eq-and-lsp/68138 .
Your proposal seems to be basically the status quo, where Hashable is a protocol defining the __hash__ method. A problem with that status quo is that object is hashable, so a subclass that is not hashable is (correctly!) marked by type checkers as having an invalid override. You propose to allow that unsafe override. Mike's proposal in the linked thread was instead to say that object has __hash__ of type None | () -> int, with a special case to allow instances of exactly object to be hashable. srittau commented on May 12, 2026 CollaboratorAuthor More actions Considering that the status quo is the status quo (and mirrors the implementation, i.e. classes are hashable by default), documenting this would be a big step forward. randolf-scholz commented on May 12, 2026
Sub-classes inherit their parent's hashability, unless overridden.
This doesn't match runtime, as cpython removes __hash__ if a subclass implements __eq__, but not __hash__:
>>> class Foo: ...
...
>>> hash(Foo())
8496821607350
>>> class Bar(Foo):
... def __eq__(self, other): return False
...
>>> hash(Bar())
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
hash(Bar())
~~~~^^^^^^^
TypeError: unhashable type: 'Bar'
This is exactly what makes static typing for __hash__ so tricky.. srittau commented on May 13, 2026 CollaboratorAuthor More actions
This doesn't match runtime, as cpython removes __hash__ if a subclass implements __eq__, but not __hash__:
This is then something that type checkers also need to replicate and should be added to the list above, according to the philosophy "special casing in the Python interpreter means special casing in type checkers".
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|