`type[Any]` not considered `Hashable`
from typing import Hashable, Any
t1: type[Any] = type
h1: Hashable = t1 # error: Incompatible types in assignment (expression has type "Type[Any]", variable has type "Hashable")
Which seems strange because type[type] is considered Hashable, demonstrating that a type can be Hashable.
This is why it happens:
from typing import Hashable, Any
t1: type[Any] = type
reveal_type(type.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int"
reveal_type(t1.__hash__) # N: Revealed type is "def () -> builtins.int"
And def () -> builtins.int is not compatible with Hashable 🤔
I will try to fix t1.__hash__ inference.
No, I was wrong. The same works with just a slight modification:
from typing import Hashable, Any
t1: type = type
reveal_type(type.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int"
reveal_type(t1.__hash__) # N: Revealed type is "def () -> builtins.int"
h1: Hashable = t1 # no error
Moreover, this also works:
from typing import Hashable, Any
t1: type[type] = type
h1: Hashable = t1
Related #11469
I was about to report the same problem for the @cache decorator case, and just want to chime in that Type[_T] for TypeVar _T is also a problem. I see discussion of Type[Any] and type[type] but it's not clear to me if that covers the type var case as well.
I think a type[T] where the upper bound of T produces a Hashable type[T] (default upper bound is object, and type[object] is hashable) should be considered Hashable.
There are also some problems with using assert statements to work around this, as in the following:
instance: Type[object]
assert isinstance(instance, Hashable) # mypy thinks this is unsatisfiable
... # mypy thinks this is unreachable
More details may be found in the duplicate bug #12993.
Just chiming in that this is still an open issue as of Mypy 1.7.0 (Python 3.12.0).
from typing import Any, Hashable
def foo(t: type) -> None:
x: Hashable = t # OK
def bar(t: type[Any]) -> None:
x: Hashable = t # error
To give a reproducer of TypeVar usage (which could be included in tests):
from functools import lru_cache
from typing import TypeVar
T = TypeVar("T")
@lru_cache
def foo(t):
...
def check(t: type[T]):
foo(t) # error: Argument 1 to "__call__" of "_lru_cache_wrapper" has incompatible type "type[T]"; expected "Hashable" [arg-type]
check(dict[str, object])
It seems that it only considers type[T] to be hashable if T is hashable. Thus, the error disappears if doing TypeVar("T", bound=Hashable). However, that is not correct as you then can't pass dict[str, object] in the last line.
In fact, not all types are hashable: mypy-play.net
from __future__ import annotations
from typing import *
# this might seem to be reasonable:
def test_func(klass: Type[object]) -> None:
klasses: Set[Type[Any]] = set()
# ...but this isn't completely sound:
klasses.add(klass)
# (mypy 1.8.0 isn't issuing an error here, which might be the right
# call because _almost_ all types are hashable)
class SomeMeta(type):
'''this metaclass defines '__eq__()' but not '__hash__()', making
instances of it unhashable
'''
def __eq__(self, other: object) -> bool:
return NotImplemented
class SomeClass(metaclass=SomeMeta):
'''this class uses 'SomeMeta', meaning the class itself is unhashable
'''
# This will raise an exception at runtime:
hash(SomeClass) # line 31
# Traceback (most recent call last):
# ...
# TypeError: unhashable type: 'SomeMeta'
# As will the 2nd line of this paragraph (if line 31 is commented out):
some_set: Set[Type[Any]] = set()
some_set.add(SomeClass)
# Traceback (most recent call last):
# ...
# TypeError: unhashable type: 'SomeMeta'
This issues no mypy errors (ca. v1.8.0), despite being unsound. That's probably the right thing to do, but it should be a carefully weighed choice (and maybe a check which could be enabled with an --enable-flag).
(That said, there are errors issued by the example in #12993. IMHO this inconsistency is undesirable.)
(That said, there are errors issued by the example in #12993. IMHO this inconsistency is undesirable.)
Indeed, as per my comment, mypy seems to consider type[X] hashable only if X is hashable, which seems completely unrelated.
Curiously, in my example, if I change from the TypeVar to using a custom class directly (def check(t: type[Bar])), then I still get the same error, yet I don't get the error if I just call foo(Bar), which should be the same thing...
[...] mypy seems to consider
type[X]hashable only ifXis hashable, which seems completely unrelated.
I think we can all agree that the hashability of type[X] has nothing to do with the hashability of X instances.
One possible approach to determining if a value is likely Hashable: in the entire system being type-checked, is there any known example of the specified type that is not Hashable? For most projects, that would mean Type[Any/object] would be Hashable. OTOH this involves spooky action at a distance: defining a metaclass in one place could cause errors to appear on the other side of the code base.
In a way, it's a pityHashable isn't Generic-like: being able to writeHashable[Type[Any]], Hashable[Mapping[K, V]], Hashable[SomeProtocol], etc., would be quite useful. This is a special case of #213, and would like be solved by the as-yet-unnumbered PEP being drafted for that issue, which will presumably introduce a syntax like Type[Any] & Hashable.