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__
?
Everything works as is should. If you are using __metaclass__
, you are overwriting the class creation process. It looks like your monkeypatch is working for __subclasshook__
but it's only called from the __subclasshook__
function of the ABCMeta
. You can check it with this:
>>> type(Foo)
<class 'abc.ABCMeta'>
To be more explicit: the __subclasshook__
case works by accident in this example, because the metaclass's __subclasscheck__
happens to defer to the class's __subclasshook__
in some situations. The __instancecheck__
protocol of the metaclass does not ever defer to the class's definition of __instancecheck__
, which is why the monkeypatched version of __subclasshook__
does eventually get called, but the monkeypatched version of __instancecheck__
does not get called.
In more details: If you are creating a class with the metaclass, the type of the class will be the metaclass. In this case the ABCMeta
. And the definition of the isinstance()
say the following: 'isinstance(object, class-or-type-or-tuple) -> bool', which means the instance checking will be executed on the given class, type or tuple of classes/types. In this case the isinstance check will be done on the ABCMeta
(ABCMeta.__instancecheck__()
will be called). Because the monkeypatch was applied to the Foo
class and not the ABCMeta
, the __instancecheck__
method of Foo
will never run. But the __instancecheck__
of the ABCMethod
will call the __subclasscheck__
method of itself, and this second method will try the validaion by executing the __subclasshook__
method of created class (in this example Foo
).
In this case you can get the desired behavior if you overwrite the functions of the metaclass like this:
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 the updated code:
True
True
True
True
Another approach would be to define your own class to serve as the metaclass, and create the kind of __instancecheck__
protocol that you're looking for, so that it does defer to the class's definition of __instancecheck__
when the metaclass's definition hits some failure criteria. Then, set __metaclass__
to be that class inside of Foo
and your existing decorator should work as-is.
More info: A good post about metaclasses
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With