Static attribute that is True for a specific class only, and False for its children in Python
Problem
Let's say I have a class Root
and you want to access (for example, initialize) all of its subclasses. But there might be some subclasses that need to be ignored programmatically.
Example
class Root(object):
pass
class Parent(Root):
ignore_me = True
class Child(Parent):
pass
def get_subclasses(klass):
result = klass.__subclasses__()
for subclass in result:
result += get_subclasses(subclass)
return result
subs = [sub for sub in get_subclasses(Root) if not sub.ignore_me]
So, I want the class Child
to be listed subs
as opposed to the class Parent
.
A trivial solution
Of course, I could define an attribute ignore_me
for each subclass, but the point is, I want to isolate the subclasses from this detail so they don't even know about it.
Question
How can I achieve the goal by simply defining an attribute ignore_me
in the class only Parent
?
source to share
You can use if sub.__dict__.get('ignore_me', False)
to check if a property is visible ignore_me
directly in a given subclass (not inherited).
However, you will still have to do it differently, because it __subclasses__
only returns immediate subclasses (as documented ). If you want to overwrite all descendant classes, you will need to write code that recursively calls __subclasses__
for each class in the hierarchy. Something like that:
def getSubs(cls):
for sub in cls.__subclasses__():
if not sub.__dict__.get('ignore_me', False):
yield sub
for desc in getSubs(sub):
yield desc
Then:
>>> list(getSubs(Root))
[<class '__main__.Child'>]
source to share
Here's another try:
class SubclassIgnoreMark(object):
def __init__(self, cls_name):
super(SubclassIgnoreMark, self).__init__()
self._cls_name = cls_name
def __get__(self, instance, owner):
return owner.__name__ == self._cls_name
def IgnoreSubclass(klass):
setattr(klass, 'ignore_me', SubclassIgnoreMark(klass.__name__))
return klass
With this, I can just decorate the class Parent
with IgnoreSubclass
decorator ...
@IgnoreSubclass
class Parent(Root):
pass
class Child(Parent):
pass
... and then:
>>> Parent.ignore_me
True
>>> Child.ignore_me
False
source to share
It's actually pretty simple:
class Parent(Root):
@classmethod
def ignore(cls):
return cls == Parent
class Child(Parent):
pass
>>> Parent().ignore()
True
>>> Child().ignore()
False
If you want the class to pass it to the subclass, just replace return cls == Parent
with return True
.
source to share
You can try using virtual subclasses and override virtual subclass subclasses.
from abc import ABCMeta
class Ignore(object):
__metaclass__ = ABCMeta
@classmethod
def __subclasshook__(ignore_cls, cls):
"""only direct, and registered lasses are considered a subclass"""
return cls in ignore_cls._abc_registry
class Root(object):
pass
class Parent(Root):
pass
Ignore.register(Parent) # slightly more elegant in python 3 as register can be used as a decorator
class Child(Parent):
pass
print(Parent, issubclass(Parent, Ignore))
print(Child, issubclass(Child, Ignore))
def get_subclasses(klass):
result = klass.__subclasses__()
for subclass in result:
result += get_subclasses(subclass)
return result
result = get_subclasses(Root)
print [cls for cls in result if not issubclass(cls, Ignore)]
source to share