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

?

+3


source to share


4 answers


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'>]

      

+2


source


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

      

+1


source


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

.

+1


source


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

      

0


source







All Articles