I've got the following class hiearchy:
class Acting: pass
class Observing: pass
class AgentMeta(type):
def __instancecheck__(self, instance: Any) -> bool:
return isinstance(instance, Observing) and isinstance(instance, Acting)
class Agent(Acting, Observing, metaclass=AgentMeta): pass
class GridWorld: pass
class GridMoving(Acting, GridWorld): pass
class GridObserving(Observing, GridWorld): pass
class Blocking(GridMoving, GridObserving): pass
class Broadcasting(Agent, GridWorld): pass
I set it up this way so that I can check this
isinstance(Blocking(), Agent) -> True
instead of this
isinstance(Blocking(), Observing) and isinstance(Blocking(), Acting)
However, now I have the undesired side effect that Blocking is an instance of Broadcasting.
isinstance(Blocking(), Broadcasting) -> True
My understanding is that this is because Braodcasting inherits from Agent, which has the modification on the __instancecheck__ from the metaclass. I don't really understand metaclasses well enough to come up with an elegant solution, but I would like to do the following:
- When I want to check if something is both
ActingandObserving, I would like to shortcut this by just checking if it isAgent. - I would like to be able to inherit from
Agentbut still have theisinstancecheck the actual class and not use the__instancecheck__fromAgentMeta.
Is this possible? Are metaclasses overkill here?
Yes. What you write in a metaclass method is just plain Python code, so, in this case, the only thing you need, is to fine-tune your code inside the
__instancecheck__method, by using more checks until you get your desired effect matched.Currently, all that you require is that the instance being check inherit from two other classes - very well.
What you require, for example, is that it inherit from the two other classes, but if the class being checked against itself (the first parameter in
__instancecheck__is not the first class in the inheritance chain to have AgentMeta as the metaclass, it should fail). (That is what I got from your text - your actual needs may need some further fine tuning)So, I suggest improving the code in
__instancecheck__to this:I used a shortened expression which might look confusing - but basically what
sum(isinstance(parent_cls, __class__) for parent_cls in cls.__mro__)does is: walk all superclasses of the class that is being asked for, including itself. Sum the truth values when that class is anAgentMeta, i.e.: if AgentMeta is the metaclass of that class, count 1, else count 0. If this count differs from 1, then the class being checked is derived from the class that first hadmetaclass=AgentMetain the declaration, and you want the "instancecheck" to return False.Another option is to leave
__instancecheck__as is in your code, and simply revert and not apply the metaclass for derived classes of the firstAgentMetaimplementing class. This can be done by writting the metaclass__new__method instead: