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

isinstance(obj, Hashable) raises TypeError when both obj and type(obj) are unhashable #129589

Open
finite-state-machine opened this issue Feb 2, 2025 · 2 comments
Labels
extension-modules C modules in the Modules dir type-bug An unexpected behavior, bug, or error

Comments

@finite-state-machine
Copy link
Contributor

finite-state-machine commented Feb 2, 2025

Bug report

Bug description:

Assumption: isinstance(anything, any_type) should never raise.

The problem appears to be in _abc.c function _abc__abc_subclasscheck_impl (known at runtime as _abc._abc_subclasscheck()), which reads (in part):

    /* 1. Check cache. */
    incache = _in_weak_set(impl->_abc_cache, subclass);

The implementation assumes subclass is hashable. That's almost always true of classes, but not guaranteed.

Reproduction case:

from __future__ import annotations
from collections.abc import Hashable

class UnhashableMeta(type):
    def __eq__(self, other: object) -> bool:
        return super().__eq__(other)

class UnhashableClass(metaclass=UnhashableMeta):
    def __eq__(self, other: object) -> bool:
        return super().__eq__(other)

# any non-hashable instance of a non-hashable class
#         ┌────────┴────────┐
isinstance(UnhashableClass(), Hashable)

Traceback for (cpython) Python 3.13.1:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File ".../bug.py", line 14, in <module>
    isinstance(UnhashableClass(), Hashable)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen abc>", line 119, in __instancecheck__
  File "<frozen abc>", line 123, in __subclasscheck__
TypeError: unhashable type: 'UnhashableMeta'

Tracebacks are nearly identical with Python 3.8, 3.9, 3.10, 3.11, and 3.12, as well as Python 3.14.0a4.

CPython versions tested on:

3.13, 3.12, 3.11, 3.10, 3.9, 3.14

Operating systems tested on:

macOS

@finite-state-machine finite-state-machine added the type-bug An unexpected behavior, bug, or error label Feb 2, 2025
@picnixz picnixz added the extension-modules C modules in the Modules dir label Feb 2, 2025
@picnixz
Copy link
Member

picnixz commented Feb 2, 2025

Hard to investigate the docs and specs on mobile so I'm deferring it to my fellow core developers. I don't know who to tag for this one but I guess @JelleZijlstra, @sobolevn or @AlexWaygood may have insights on this matter.

As for me, I guess we could check that subclass is hashable beforehand and return false if it's not? By the way, is this class with this custom metaclass a "broken" class or not?

In addition, is

Assumption: isinstance(anything, any_type) should never raise.

an assumption that we should make or that is being documented? for instance, issubclass fails if the first argument is not a type I think (or the second one, or both, I don't remember) so maybe isinstance should also behave like this for "broken" classes (again, that's assuming the class and metaclass in this reproducer are broken, if it's not considered as broken, then we might indeed have a bug).

@JelleZijlstra
Copy link
Member

It looks like the hashing is done for a cache that is used to optimize the check. If the class is not hashable, we should simply skip using the cache. This will be slower but that seems reasonable.

Assumption: isinstance(anything, any_type) should never raise.

isinstance() can invoke arbitrary user-written code, so this isn't a guarantee we can make at the language level. However, it's definitely a good guideline to follow when implementing __subclasscheck__ yourself, and I think we should try to make this guarantee hold for all standard library instances of any_type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extension-modules C modules in the Modules dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants