__instancecheck__ - 覆盖无效果 - 我做错了什么?

7
我想让我的类看起来像是一个不同的对象,以规避我正在使用的软件包中的懒惰类型检查。更具体地说,在实际情况下,我正在尝试使我的对象看起来像另一个对象的实例(在我的例子中是元组),而实际上它甚至不是这个对象的派生。
为了达到这个目的,我计划重写__isinstance__方法,根据文档, 这样做应该能够如我所愿。然而,似乎我并没有理解如何确切地执行这个操作,因为我的尝试一直不成功。
下面是一个SSCCE,它应该使所有情况下的isinstance返回False,但实际上并没有。
class FalseInstance(type):
    def __instancecheck__(self, instance):
        return False

class Foo(metaclass=FalseInstance):
    pass

g = Foo()
isinstance(g, Foo)
> True

我做错了什么?

你的非元组类是什么样子?它是否实现了你的包所需的所有元组方法?我能想到的唯一真正的解决方案是子类化元组,但覆盖其所有方法。 - Dunes
@Dunes 谢谢,我已经调整了问题。现在我也明白了为什么我提供的代码不起作用。然而,如果我现在翻转示例并更改它,使得__isinstance__始终返回_True_,它仍然不能按照我期望的方式工作:isinstance(g, int)仍然返回False。这里的问题是什么? - Nearoo
@Dunes 对于你的第二个问题 - tuples 是不可变的,因此使用起来有点麻烦,对吧?我还不知道包需要使用元组的哪些方面才能正常工作,但我想在抛出异常时添加它们... - Nearoo
2个回答

10
除了涉及到 __metaclass__ 和快速精确类型匹配的问题之外,__instancecheck__ 的工作方向与您尝试做的相反。一个类的 __instancecheck__ 检查其他对象是否被认为是该类的虚拟实例,而不是该类的实例是否被认为是其他类的虚拟实例。
如果您想让您的对象在 isinstance 检查中撒谎(您真的不应该这样做),那么要做的方法就是对 __class__ 进行撒谎,而不是实现 __instancecheck__。
class BadIdea(object):
    @property
    def __class__(self):
        return tuple

print(isinstance(BadIdea(), tuple)) # prints True

顺便提一下,如果您想获取对象的实际类型,请使用type而不是检查__class__isinstance


哎呀,这真是太糟糕了。我想知道这是否是一个实现细节,导致isinstance被欺骗了。 - jsbueno
1
@jsbueno:我不确定。标准库依赖它,特别是在unittest.mock中,但isinstance__class__的文档没有提到它。C API文档提到了它,但C API是实现细节。 - user2357112
这直接违反了PEP的描述,可能是个bug。但是,是的,一个快速路径会在调用__instancecheck__之前首先检查直接类型。 - Martijn Pieters
1
请参见这个错误报告,目前似乎还没有达成关于这是一个错误还是文档问题的共识。 - Martijn Pieters

7
如果你在FalseInstance.__instancecheck__中添加一个打印语句,你会发现它甚至没有被调用。然而,如果您调用isinstance('str', Foo),那么您将看到FalseInstance.__instancecheck__确实被调用了。
这是因为在isinstance的实现中进行了一种优化,即当type(obj) == given_class时立即返回True
int
PyObject_IsInstance(PyObject *inst, PyObject *cls)
{
    _Py_IDENTIFIER(__instancecheck__);
    PyObject *checker;

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

来自Python源代码


(这是一个链接,无法翻译)

真的吗?如果不是为了改变这种行为,提供__isinstance__有什么意义呢? - Nearoo
看起来在Python 2.7中它可以正常工作...:https://dev59.com/XGcs5IYBdhLWcg3wGgJc#13135792 - Moshe Slavin
5
@Nearoo说:__instancecheck__的主要目的是使更多的isinstance检查通过,而不是减少。另外,没有__isinstance__ - user2357112
然而,如果我现在翻转这个例子并将其更改为__isinstance__始终返回_True_,它仍然不能按照我期望的方式工作:isinstance(g,int)现在返回False。问题出在哪里? - Nearoo
1
@Nearoo:没有__isinstance__只有__instancecheck__,它的工作方向与您尝试做的相反。 - user2357112
抱歉,我是指 __instancecheck__。但你说得对,我搞错了。这样就清楚了,非常感谢! - Nearoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接