Why will __subclasshook__ be disabled in the metaclass, but __instancecheck__ cannot?

Here's an example of a toy trying to create a decorator that allows you to declare attribute names that must be required for parts of the "interface check" using the standard __subclasshook__ and __instancecheck__ .

It seems to work as expected when I decorate the Foo class. I am creating a Bar class that is not associated with Foo , but has the necessary attributes, and it correctly satisfies isinstance(instance_of_bar, Foo) == True .

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

Here is the code. Note that, given that the example with the Bar instance works, selecting the โ€œmonkeypatchโ€ function __subclasshook__ in the Foo class (which has a metaclass) works fine. Therefore, it does not seem that you need to define a function directly in the definition of a metaclass class.

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

Print

 True True False False 

This is an example of a game - I'm not looking for a better way to implement my attrdict class. The Bar example demonstrates monkeypatched __subclasshook__ . The other two examples demonstrate __instancecheck__ failure for instances that have simple instance attributes to validate. In such cases, __instancecheck__ is not even called.

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

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

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

+8
python interface subclass abc
source share
1 answer

Everything works as it should. If you use __metaclass__ , you are rewriting the process of creating the class. It looks like your monkeypatch works for __subclasshook__ , but it only __subclasshook__ called from the __subclasshook__ function. You can check it as follows:

 >>> type(Foo) <class 'abc.ABCMeta'> 

To be more explicit: the __subclasshook__ case works randomly in this example, because the metaclass __subclasscheck__ in some situations __subclasshook__ class. The __instancecheck__ metaclass protocol never __instancecheck__ to defining the __instancecheck__ class, so the final version of the __subclasshook__ monkey is eventually passed, but both versions with the __instancecheck__ not called.

In more detail: if you create a class with a metaclass, the class type will be a metaclass. In this case, ABCMeta . And the definition of isinstance() says the following: "isinstance (object, class-or-type-or-tuple) โ†’ bool", which means that instance checking will be performed on a given class, type or tuple of classes / types. In this case, the isinstance check will be performed on ABCMeta ( ABCMeta.__instancecheck__() will be called). Since monkeypatch was applied to the Foo class, not ABCMeta , the __instancecheck__ Foo method will never be run. But __instancecheck__ ABCMethod will call the __subclasscheck__ method itself, and this second method will try to execute validaion by executing the __subclasshook__ method of the created class (in this example, Foo ).

In this case, you can get the desired behavior if you replace the metaclass functions as follows:

 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.__metaclass__.__subclasshook__ = classmethod(__subclasshook__) Base.__metaclass__.__instancecheck__ = classmethod(__instancecheck__) return Base return decorator 

And the output with updated code:

 True True True True 

Another approach would be to define your own class to act as a metaclass and create the kind of protocol __instancecheck__ that you are looking for to refer to the class definition of __instancecheck__ when the metaclass definition has some rejection criteria. Then set __metaclass__ such a class inside Foo , and your existing decorator should work as is.

More info: Good article on metaclasses

+2
source share

All Articles