Why can `__subclasshook__` be monkeypatched onto the metaclass but `__instancecheck__` cannot?

Viewed 2494

Here is a toy example of trying to create a decorator that allows declaration of attribute names which should be required parts of "interface checking" along the standard __subclasshook__ and __instancecheck__ patterns.

It seems to work as expected when I decorate the Foo class. I make a Bar class, unrelated to Foo, but which has the needed attributes, and it correctly satisfies isinstance(instance_of_bar, Foo) == True.

But then as another example, I make a subclass of dict augmented so that the key values will be accessible with getattr syntax as well (e.g. a dict where d['a'] can be replaced with d.a to get the same result). In this case, the attributes are just instance attributes, so __instancecheck__ should work.

Here is the code. Note that given that the example with the instance of Bar works, the choice to "monkeypatch" the __subclasshook__ function into the Foo class (that has a metaclass) works fine. So it does not seem that one must define the function directly in the class definition of the metaclass.

#Using Python 2.7.3

import abc
def interface(*attributes):
    def decorator(Base):

        def checker(Other):
            return all(hasattr(Other, a) for a in attributes)

        def __subclasshook__(cls, Other):
            if checker(Other):
                return True
            return NotImplemented

        def __instancecheck__(cls, Other):
            return checker(Other)

        Base.__subclasshook__ = classmethod(__subclasshook__)
        Base.__instancecheck__ = classmethod(__instancecheck__)
        return Base

    return decorator

@interface("x", "y")
class Foo(object):
    __metaclass__ = abc.ABCMeta
    def x(self): return 5
    def y(self): return 10

class Bar(object):
    def x(self): return "blah"
    def y(self): return "blah"

class Baz(object):
    def __init__(self):
        self.x = "blah"
        self.y = "blah"

class attrdict(dict):
    def __getattr__(self, attr):
        return self[attr]

f = Foo()
b = Bar()
z = Baz()
t = attrdict({"x":27.5, "y":37.5})

print isinstance(f, Foo)
print isinstance(b, Foo)
print isinstance(z, Foo)
print isinstance(t, Foo)

Prints:

True
True
False
False

This is just a toy example -- I'm not looking for better ways to implement my attrdict class. The Bar example demonstrates the monkeypatched __subclasshook__ working. The other two examples demonstrate failure of __instancecheck__ for instances that have mere instance attributes to check on. In those cases, __instancecheck__ is not even called.

I can manually check that the condition from my __instancecheck__ function is satisfied by the instance of attrdict (that is, hasattr(instance_of_attrdict, "x") is True as needed) or z.

Again, it seems to work fine for the instance of Bar. This suggests that __subclasshook__ is being correctly applied by the decorator, and that patching a custom __metaclass__ is not the issue. But __instancecheck__ does not seem to be called in the process.

Why can __subclasshook__ be defined outside of the class definition of the metaclass and added later, but not __instancecheck__?

1 Answers
Related