为什么__instancecheck__并不总是根据参数调用?

scd*_*dmb 11 python python-3.x

有这个代码:

class Meta(type):
  def __instancecheck__(self, instance):
    print("__instancecheck__")
    return True

class A(metaclass=Meta):
  pass


a = A()
isinstance(a, A) # __instancecheck__ not called
isinstance([], A) # __instancecheck__ called
Run Code Online (Sandbox Code Playgroud)

为什么__instancecheck__要求[]争论而不是a争论呢?

dno*_*zay 8

PyObject_IsInstance 快速测试完全匹配.

Objects/abstract.c:

int
PyObject_IsInstance(PyObject *inst, PyObject *cls)
{
    static PyObject *name = NULL;

    /* Quick test for an exact match */
    if (Py_TYPE(inst) == (PyTypeObject *)cls)
        return 1;
// ...
Run Code Online (Sandbox Code Playgroud)

不喜欢快车道?你可以尝试这个(风险自负):

>>> import __builtin__
>>> def isinstance(a, b):
...     class tmp(type(a)):
...          pass
...     return __builtin__.isinstance(tmp(), b)
... 
>>> __builtin__.isinstance(a, A)
True
>>> isinstance(a, A)
__instancecheck__
True
Run Code Online (Sandbox Code Playgroud)


7st*_*tud 5

我认为 PEP 描述__instancecheck__()是错误的。PEP 3119 说:

这里提出的主要机制是允许重载内置函数 isinstance() 和 issubclass()。重载的工作原理如下:调用 isinstance(x, C) 首先检查是否 C.__instancecheck__存在,如果存在,则调用C.__instancecheck__(x) 而不是其正常实现。

你可以写:

class C:
    def do_stuff(self):
        print('hello')

C.do_stuff(C())
Run Code Online (Sandbox Code Playgroud)

所以根据上面来自 PEP 的引用,你应该能够写

class C:
    @classmethod
    def __instancecheck__(cls, x):
        print('hello')


C.__instancecheck__(C())

--output:--
hello
Run Code Online (Sandbox Code Playgroud)

但是 isinstance() 不会调用该方法:

class C:
    @classmethod
    def __instancecheck__(cls, y):
        print('hello')


x = C()
isinstance(x, C)

--output:--
<nothing>
Run Code Online (Sandbox Code Playgroud)

然后 PEP 继续说:

这些方法旨在在元类是(派生自)ABCMeta...的类上调用。

好的,让我们试试:

import abc

class MyMeta(abc.ABCMeta):  #A metaclass derived from ABCMeta
    def __instancecheck__(cls, inst):
        print('hello')
        return True

class C(metaclass=MyMeta):  #A class whose metaclass is derived from ABCMeta
    pass


x = C()
C.__instancecheck__(x)

--output:--
hello
Run Code Online (Sandbox Code Playgroud)

但再次 isinstance() 不会调用该方法:

isinstance(x, C)

--output:--
<nothing>
Run Code Online (Sandbox Code Playgroud)

结论:PEP 3119 需要重写——连同“数据模型”文档。