Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why __instancecheck__ is not always called depending on argument?

There is this code:

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

Why __instancecheck__ is called for [] argument but not for a argument?

like image 699
scdmb Avatar asked Jul 18 '13 18:07

scdmb


2 Answers

PyObject_IsInstance does a quick test for exact match.

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

don't like the fast path? you can try this (at your own risk):

>>> 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
like image 63
dnozay Avatar answered Sep 18 '22 13:09

dnozay


I think that the PEP describing __instancecheck__() is faulty. PEP 3119 says:

The primary mechanism proposed here is to allow overloading the built-in functions isinstance() and issubclass(). The overloading works as follows: The call isinstance(x, C) first checks whether C.__instancecheck__ exists, and if so, calls C.__instancecheck__(x) instead of its normal implementation.

You can write:

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

C.do_stuff(C())

So based on the quote above from the PEP, you should be able to write

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


C.__instancecheck__(C())

--output:--
hello

But isinstance() does not call that method:

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


x = C()
isinstance(x, C)

--output:--
<nothing>

The PEP then goes on to say:

These methods are intended to be be called on classes whose metaclass is (derived from) ABCMeta...

Okay, let's try that:

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

But once again isinstance() does not call that method:

isinstance(x, C)

--output:--
<nothing>

Conclusion: PEP 3119 needs to be rewritten--along with the "Data Model" docs.

like image 39
7stud Avatar answered Sep 21 '22 13:09

7stud