Visitar URL original
Special-casing `__hash__` · Issue #2284 · python/typing · GitHub
Skip to content

Special-casing __hash__ #2284

Description

@srittau

The 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.

Activity

  1. JelleZijlstra commented on May 12, 2026

    @JelleZijlstra
    Member

    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.

  2. srittau commented on May 12, 2026

    @srittau
    CollaboratorAuthor

    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.

  3. randolf-scholz commented on May 12, 2026

    @randolf-scholz
    Contributor

    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..

  4. srittau commented on May 13, 2026

    @srittau
    CollaboratorAuthor

    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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions