Skip to content

MyPy does not enforce Hashability #2455

Open

Description

@rowillia

For example, the following code is illegal in Python 3 (implementing __eq__ shadows your __hash__ implementation with None in Python 3)

class Foo(object):
  def __init__(self, x):
    self.x = x
  def __eq__(self, other):
    if not isinstance(other, Foo):
      return NotImplemented
    return self.x == other.x

set_of_foo = {Foo(1), Foo(2)}

Poking around the code, AFAICT mypy doesn't do any Hashable checks. I don't see any other Protocols being checked in checker.py. Is there any existing tickets around supporting Protocols in mypy?

Activity

  1. gvanrossum commented on Nov 15, 2016

    @gvanrossum
    Member

    Other than python/typing#11, not that I know.

  2. ilevkivskyi commented on Dec 18, 2019

    @ilevkivskyi
    Member

    Another example appeared in #8150. Also I think it makes to raise priority to normal as this is relatively common cause of confusion.

  3. erictraut commented on Aug 10, 2023

    @erictraut

    Pyright recently added support for this feature. It wouldn't have been possible without some significant modifications to typeshed stubs. See this, this, and this for details. Now that these changes are in typeshed, it should be relatively easy to add this check to mypy.

  4. finite-state-machine commented on Oct 30, 2023

    @finite-state-machine

    In case it's useful to have gists/tests for this:

    With regular objects:

    from __future__ import annotations
    from typing import *
    from typing_extensions import (
            reveal_type,
            )
    
    class SomeClass:
    
        def __eq__(self, other: object) -> bool:
            # defining '__eq__()' without defining '__hash__()' implicitly
            # makes instances of this class unhashable, and sets '__hash__'
            # to 'None'
            return super().__eq__(other)
    
    instance = SomeClass()
    
    hash(instance)
            # mypy actual behavior:    no error issued
            # mypy expected behavior:  issue an error, since this fails at runtime
            # runtime behavior:        TypeError: unhashable type: 'SomeClass'
    
    reveal_type(type(instance).__hash__)
            # mypy actual behavior:    Revealed type is "def (self: builtins.object) -> builtins.int"
            # mypy expected behavior:  Revealed type is "None"
            # at runtime:              Runtime type is 'NoneType'

    I had been under the impression that types were always hashable, but it seems that's not always the case if a metaclass is in play – there's nothing different or special about this case:

    from __future__ import annotations
    from typing import *
    from typing_extensions import (
            reveal_type,
            )
    
    class SomeMeta(type):
    
        def __eq__(self, other: object) -> bool:
            # defining '__eq__()' without defining '__hash__()' implicitly
            # makes instances of this metaclass unhashable, and sets
            # '__hash__' to 'None'
            return super().__eq__(other)
    
    
    class SomeClass(metaclass=SomeMeta):
    
        pass
    
    
    hash(SomeClass)
            # mypy actual behavior:    no error issued
            # mypy expected behavior:  issue an error, since this fails at runtime
            # runtime behavior:        TypeError: unhashable type: 'SomeMeta'
    
    
    reveal_type(type(SomeClass).__hash__)
            # mypy actual behavior:    Revealed type is "def (self: object) -> int"
            # mypy expected behavior:  Revealed type is "None"
            # at runtime:              Runtime type is 'NoneType'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions