Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MyPy does not enforce Hashability #2455

Open
rowillia opened this issue Nov 15, 2016 · 4 comments
Open

MyPy does not enforce Hashability #2455

rowillia opened this issue Nov 15, 2016 · 4 comments

Comments

@rowillia
Copy link
Contributor

rowillia commented Nov 15, 2016

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?

@gvanrossum
Copy link
Member

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

@ilevkivskyi
Copy link
Member

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

@erictraut
Copy link

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.

@finite-state-machine
Copy link

finite-state-machine commented Oct 30, 2023

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
Projects
None yet
Development

No branches or pull requests

6 participants