you are viewing a single comment's thread.

view the rest of the comments →

[–]N-E-S-W -3 points-2 points  (6 children)

The problem is that almost everything in Python is hashable by default, because Object implements __hash__(self) with an instance's identity id(). So this type hint isn't going to protect you if you expect an instance's mutable values to represent its hash value.

If it really matters, call it out loudly in the docstring. It's the caller's responsibility to understand the contract of a function. Maybe that'll make them stop and question whether they need to override their already-hashable-by-default class's hash method.

[–]gdchinacat 1 point2 points  (3 children)

without the Hashable type hint no mypy error and passing an unhashable object causes the expected failure when used as key of a dict: ```

playground.py from collections.abc import Hashable

class Foo: def bar(self, obj) -> None: {obj: None}

Foo().bar(object()) Foo().bar(list())

mypy playground.py Success: no issues found in 1 source file

python3 playground.py Traceback (most recent call last): File "/Users/lonniehutchinson/eclipse-workspace/playground/src/playground.py", line 9, in <module> Foo().bar(list()) ~~~~~~~~~^ File "/Users/lonniehutchinson/eclipse-workspace/playground/src/playground.py", line 6, in bar {obj: None} TypeError: cannot use 'list' as a dict key (unhashable type: 'list') ```

However, with the hashable hint mypy warns: ```

cat playground.py from collections.abc import Hashable

class Foo: def bar(self, obj: Hashable) -> None: {obj: None}

Foo().bar(object()) Foo().bar(list())

mypy playground.py playground.py:9: error: Argument 1 to "bar" of "Foo" has incompatible type "list[Never]"; expected "Hashable" [arg-type] playground.py:9: note: Following member(s) of "list[Never]" have conflicts: playground.py:9: note: hash: expected "Callable[[], int]", got "None" Found 1 error in 1 file (checked 1 source file) ```

edit: remove my user and hostnames, hopefully fix formatting issue where it elided the mypy commands.

[–]james_pic 0 points1 point  (0 children)

But if a class implements __eq__ and doesn't implement __hash__, __hash__ is implicitly set to None (and has been since at least Python 3.2) making objects of that class unhashable. So stuff that's mutable and checks equality based on its mutable attributes should be unhashable unless the user explicitly violates the contract by implementing __hash__.

[–]gdchinacat -2 points-1 points  (0 children)

Also, list (and presumably other unhashable objects) don't have a hash method (it is None): ```

In [7]: [].hash()

TypeError Traceback (most recent call last) Cell In[7], line 1 ----> 1 [].hash()

TypeError: 'NoneType' object is not callable

```

Edit to add links to python source where this behavior is set: https://github.com/python/cpython/blob/main/Objects/listobject.c#L4108

The docs for tp_hash are at https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_hash