【问题标题】:Why isn't __instancecheck__ being called?为什么不调用 __instancecheck__ ?
【发布时间】:2026-02-03 22:45:01
【问题描述】:

我有以下python3代码:

class BaseTypeClass(type):
    def __new__(cls, name, bases, namespace, **kwd):
        result = type.__new__(cls, name, bases, namespace)
        print("creating class '{}'".format(name))
        return result

    def __instancecheck__(self, other):
        print("doing instance check")
        print(self)
        print(other)
        return False


class A(metaclass=BaseTypeClass):
    pass

print(type(A))
print(isinstance(A(), A))

当我在 Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32 上运行它时,我得到以下输出

creating class 'A'
<class '__main__.BaseTypeClass'>
True

为什么不输出doing instance checkdocumentation 表示需要在元类而不是类本身上定义 __instancecheck__ 方法,我在这里已经完成了。我什至验证了自从打印了creating class 'A' 以来正在使用元类。但是,当我调用 isinstance 时,它似乎使用的是默认实现,而不是我在元类中定义的实现。

我可能没有正确使用元类,但我不知道我在哪里犯了错误。

【问题讨论】:

  • 让我们尝试解决这个“实施细节”...
  • 作为一个奇怪的旁注,如果您将__instancecheck__ 定义为普通类的方法,则该类的实例可以用作isinstance 的第二个参数

标签: python python-3.x class python-3.6 metaclass


【解决方案1】:

isinstance 函数可以快速检查作为参数提供的实例的类型是否与类的类型相同。如果是这样,它会提前返回并且不会调用您的自定义__instancecheck__

这是一种优化,用于避免在不需要时对__instancecheck__(它是 Pythonland 代码)进行昂贵的调用。

你可以看到specific test in PyObject_IsInstance,在CPython实现中处理isinstance调用的函数:

/* Quick test for an exact match */
if (Py_TYPE(inst) == (PyTypeObject *)cls)
    return 1;

当然,当测试不是 True 时,您的 __instancecheck__ 会正确触发:

>>> isinstance(2, A)
doing instance check
<class '__main__.A'>
2
False

我不确定这是否是特定于实现的,但我认为是这样,因为在 the corresponding PEP sectionisinstance 的文档中都没有提到这一点。


有趣的是:issubclass 实际上并没有这种行为。由于它的实现,它总是调用__subclasscheck__。不久前,我打开了an issue,但仍在等待中。

【讨论】:

  • 开枪,我应该认为是这种情况,并尝试用其他一些情况进行测试。谢谢!
  • 嗯。这似乎与文档相矛盾。 __instancecheck____subclasscheck__ 上的 data model docs 也没有提到这种情况。我觉得if (Py_TYPE(inst) == (PyTypeObject *)cls) 检查应该移到if (PyType_CheckExact(cls)) 块中;它会更好地匹配文档,并且可能不会显着减慢速度,尽管我没有对其进行基准测试。
  • 这是一个奇怪的情况,因为 __instancecheck____subclasscheck__ @user2357112 之间也存在差异。不久前我打开了一个issue,在那里我谈到了它们的区别(我还没有时间在 python-dev 上发布)。
【解决方案2】:

Jim 的回答似乎很到位。

但是对于出于某种奇怪的原因需要完全定制的人 instancheck (好的,现在我正在写这个,似乎有 没有正确的理由想要那个,希望我错了),一个元类可以得到 远离它,但它很棘手。

这个动态替换了实际的类 由“影子类”实例化的对象,即 是原版的克隆。这样,原生的“instancheck”总是 失败,然后调用元类。

def sub__new__(cls, *args, **kw):
    metacls = cls.__class__
    new_cls = metacls(cls.__name__, cls.__bases__, dict(cls.__dict__), clonning=cls)
    return new_cls(*args, **kw)

class M(type):
    shadows = {}
    rev_shadows = {}
    def __new__(metacls, name, bases, namespace, **kwd):
        clonning = kwd.pop("clonning", None)
        if not clonning:
            cls = super().__new__(metacls, name, bases, namespace)
            # Assumes classes don't have  a `__new__` of them own.
            # if they do, it is needed to wrap it.
            cls.__new__ = sub__new__
        else:
            cls = clonning
            if cls not in metacls.shadows:
                clone = super().__new__(metacls, name, bases, namespace)
                # The same - replace for unwrapped new.
                del clone.__new__
                metacls.shadows[cls] = clone
                metacls.rev_shadows[clone] = cls
            return metacls.shadows[cls]

        return cls

    def __setattr__(cls, attr, value):

        # Keep class attributes in sync with shadoclass
        # This could be done with 'super', but we'd need a thread lock
        # and check for re-entering.
        type.__setattr__(cls, attr, value)
        metacls = type(cls)
        if cls in metacls.shadows:
            type.__setattr__(metacls.shadows[cls], attr, value)
        elif cls in metacls.rev_shadows:
            type.__setattr__(metacls.rev_shadows[cls], attr, value)    

    def call(cls, *args, **kw):
        # When __new__ don't return an instance of its class,
        # __init__ is not called by type's __call__
        instance = cls.__new__(*args, **kw)
        instance.__init__(*args, **kw)
        return instance

    def __instancecheck__(cls, other):
        print("doing instance check")
        print(cls)
        print(other)
        return False


class A(metaclass=M):
    pass

print(type(A))
print(isinstance(A(), A))

它甚至有一种机制来同步影子类和实际类中的属性。它不支持的一件事是如果以这种方式处理的类确实实现了自定义__new__。如果这样的__new__ 使用无参数super,它开始变得棘手,因为 super 的参数不会是影子类。

【讨论】: