仅对具体 class 为 True 的静态属性,对其在 Python 中的子项为 False

Static attribute that is True only for a concrete class, and False for its children in Python

问题

假设我有一个 class Root 并且想要访问(例如初始化)它的所有子class。但是可能有一些需要以编程方式忽略的子classes。

例子

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]

所以我想要的是 Child class 包含在 subs 列表中,而不是 Parent class.

简单的解决方案

当然,我可以为每个 subclass 定义 ignore_me 属性,但关键是我想将 subclasses 从那个细节中分离出来,这样他们甚至不会意识到它。

问题

如何仅在 Parent class 中定义 ignore_me 属性来实现目标?

您可以使用 if sub.__dict__.get('ignore_me', False) 来检查 ignore_me 属性 是否直接出现在给定的子 class 中(不是继承的)。

但是,您仍然需要以不同的方式执行此操作,因为 __subclasses__ 只有 returns 立即 subclass 是(如 documented).如果要递归遍历所有后代 class,则需要编写一些代码,在层次结构中的每个 class 上递归调用 __subclasses__。像这样:

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

然后:

>>> list(getSubs(Root))
[<class '__main__.Child'>]

这是另一个尝试:

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

有了这些,我就可以用 IgnoreSubclass 装饰器装饰 Parent class...

@IgnoreSubclass
class Parent(Root):
    pass

class Child(Parent):
    pass

...然后:

>>> Parent.ignore_me
True
>>> Child.ignore_me
False

这其实很简单:

class Parent(Root):
    @classmethod
    def ignore(cls):
        return cls == Parent

    class Child(Parent):
        pass

>>> Parent().ignore()
True
>>> Child().ignore()
False

如果您想要 class 并将其转移到子 class,则只需将 return cls == Parent 替换为 return True

你可以尝试使用虚子类,重写虚子类的子类钩子。

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