Skip to content

type[Any] not considered Hashable #11470

Open

Description

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

Activity

  1. sobolevn commented on Nov 5, 2021

    @sobolevn
    Member

    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.

  2. sobolevn commented on Nov 5, 2021

    @sobolevn
    Member

    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
  3. sobolevn commented on Nov 5, 2021

    @sobolevn
    Member

    Moreover, this also works:

    from typing import Hashable, Any
    
    t1: type[type] = type
    h1: Hashable = t1
  4. KotlinIsland commented on Nov 6, 2021

    @KotlinIsland
    ContributorAuthor

    Related #11469

  5. gwk commented on Mar 21, 2022

    @gwk

    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.

  6. KotlinIsland commented on Mar 21, 2022

    @KotlinIsland
    ContributorAuthor

    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.

  7. finite-state-machine commented on Jun 17, 2022

    @finite-state-machine

    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.

  8. sg495 commented on Nov 13, 2023

    @sg495

    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
  9. Dreamsorcerer commented on Feb 12, 2024

    @Dreamsorcerer
    Contributor

    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.

  10. finite-state-machine commented on Feb 13, 2024

    @finite-state-machine

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

  11. finite-state-machine commented on Feb 13, 2024

    @finite-state-machine

    (That said, there are errors issued by the example in #12993. IMHO this inconsistency is undesirable.)

  12. Dreamsorcerer commented on Feb 13, 2024

    @Dreamsorcerer
    Contributor

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

  13. finite-state-machine commented on Feb 13, 2024

    @finite-state-machine

    [...] mypy seems to consider type[X] hashable only if X is 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.

  14. finite-state-machine commented on Feb 13, 2024

    @finite-state-machine

    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.

  15. chadrik commented on Oct 17, 2024

    @chadrik
    Contributor

    Indeed, as per my comment, mypy seems to consider type[X] hashable only if X is hashable, which seems completely unrelated.

    I'm not sure if this is true. Below are some tests with mypy 1.12. type[int] is not considered Hashable.

    from dataclasses import dataclass
    from typing import Hashable, Any, NamedTuple
    
    t1: type[Any] = type
    h1: Hashable = t1  # error: Incompatible types in assignment
    
    
    t2: type[str] = str
    h2: Hashable = t2  # success!
    
    
    @dataclass(frozen=True, unsafe_hash=True)
    class Foo:
        x: int
    
    t3: type[Foo] = Foo
    h3: Hashable = t3  # error: Incompatible types in assignment
    
    
    class Bar(NamedTuple):
        x: int
    
    t4: type[Bar] = Bar
    h4: Hashable = t4  # error: Incompatible types in assignment
    
    t5: type[int] = int
    h5: Hashable = t5  # error: Incompatible types in assignment
    
    t6: type[type] = type
    h6: Hashable = t6  # success!
    
    t7: type = int
    h7: Hashable = t7  # success!
  16. thearchitector commented on Jan 25, 2025

    @thearchitector

    i'm not entirely sure what the correct "resolution" is here / what the proper way to type this is, but fwiw, I hit this error with the following on 1.14.1:

    from functools import lru_cache
    from typing import TypeVar, Type
    
    T = TypeVar("T", bound=object)
    
    @lru_cache(maxsize=None)
    def _get_specifiable_attributes(cls: Type[T]): ...
    
    def run(dest_type: Type[T]):
        _get_specifiable_attributes(dest_type)
        # main.py:10: error: Argument 1 to "__call__" of "_lru_cache_wrapper" has incompatible type "type[T]"; expected "Hashable"  [arg-type]
    
    class FooBar:
        ...
    
    run(FooBar)

    though for some reason this works fine:

    from functools import lru_cache
    from typing import TypeVar, Type
    
    T = TypeVar("T", bound=object)
    
    @lru_cache(maxsize=None)
    def _get_specifiable_attributes(cls: Type[T]): ...
    
    class FooBar:
        ...
    
    _get_specifiable_attributes(FooBar)
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

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions