Почему __instancecheck__ не всегда вызывается в зависимости от аргумента?

Есть этот код:

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

Почему __instancecheck__ вызывается для аргумента [], но не для аргумента a?

Ответ 1

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;
// ...

Не нравится быстрый путь? вы можете попробовать это (на свой страх и риск):

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

Ответ 2

Я думаю, что PEP, описывающий __instancecheck__(), неисправен. PEP 3119 говорит:

Основной механизм, предлагаемый здесь, - это перегрузка встроенные функции isinstance() и issubclass(). Перегрузка работает следующим образом: вызов isststance (x, C) сначала проверяет, C.__instancecheck__ существует, и если это так, называет C.__instancecheck__(x)вместо обычной реализации.

Вы можете написать:

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

C.do_stuff(C())

Итак, основываясь на приведенной выше цитате из PEP, вы должны иметь возможность писать

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


C.__instancecheck__(C())

--output:--
hello

Но isinstance() не вызывает этот метод:

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


x = C()
isinstance(x, C)

--output:--
<nothing>

Затем 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

Но еще раз isinstance() не вызывает этот метод:

isinstance(x, C)

--output:--
<nothing>

Заключение: PEP 3119 необходимо переписать вместе с документами Data Model.