Python:如何在创建时向父类注册所有子类

Posted

技术标签:

【中文标题】Python:如何在创建时向父类注册所有子类【英文标题】:Python: How to register all child classes with the father class upon creation 【发布时间】:2011-10-07 11:47:41 【问题描述】:

我有 python 类树,每一个都由一个抽象基类和许多派生的具体类组成。我希望所有具体类都可以通过基类方法访问,并且我不想在子类创建期间指定任何内容。

这是我想象中的解决方案的样子:

class BaseClassA(object):
    # <some magic code around here>
    @classmethod
    def getConcreteClasses(cls):
        # <some magic related code here>

class ConcreteClassA1(BaseClassA):
    # no magic-related code here

class ConcreteClassA2(BaseClassA):
    # no magic-related code here

尽可能地,我更愿意将“魔法”写一次作为一种设计模式。我希望能够将它应用于不同场景中的不同类树(即添加一个具有“BaseClassB”及其具体类的类似树)。

感谢互联网!

【问题讨论】:

那...不是真正的解决方案... 所有具体类都在同一个源文件中吗? @Ignacio:确实这不是解决方案,这就是我想在我的场景中使用解决方案的方式。 @Triptych:不,不一定。 这能回答你的问题吗? How to auto register a class when it's defined 在类似的领域,另一个问题的核心问题是实例化直接装饰的类,而不是间接触发类本身的注册。不过感谢您的参考! 【参考方案1】:

您可以为此使用元类:

class AutoRegister(type):
    def __new__(mcs, name, bases, classdict):
        new_cls = type.__new__(mcs, name, bases, classdict)
        #print mcs, name, bases, classdict
        for b in bases:
            if hasattr(b, 'register_subclass'):
                b.register_subclass(new_cls)
        return new_cls


class AbstractClassA(object):
    __metaclass__ = AutoRegister
    _subclasses = []

    @classmethod
    def register_subclass(klass, cls):
        klass._subclasses.append(cls)

    @classmethod
    def get_concrete_classes(klass):
        return klass._subclasses


class ConcreteClassA1(AbstractClassA):
    pass

class ConcreteClassA2(AbstractClassA):
    pass

class ConcreteClassA3(ConcreteClassA2):
    pass


print AbstractClassA.get_concrete_classes()

我个人对这种魔法非常警惕。不要在你的代码中添加太多。

【讨论】:

非常优雅!我见过类似的元类解决方案,但不太灵活。谢谢亚历克斯!【参考方案2】:

您应该知道您正在寻找的部分答案是内置的。新式类自动保持对其所有子类的弱引用,这些子类可以通过__subclasses__ 方法访问:

@classmethod
def getConcreteClasses(cls):
    return cls.__subclasses__()

这不会返回子子类。如果你需要这些,你可以创建一个递归生成器来获取它们:

@classmethod
def getConcreteClasses(cls):
    for c in cls.__subclasses__():
        yield c
        for c2 in c.getConcreteClasses():
            yield c2

【讨论】:

太棒了,我不知道这种机制存在。当不需要注册的“回调”行为,而是子类树的按需迭代时,这很好。从python内置的“对象”类开始生成子类树也很好。【参考方案3】:

这是一个使用现代 python (3.6+) __init__subclass__ 在PEP 487 中定义的简单解决方案。它允许您避免使用元类。

class BaseClassA(object):
    _subclasses = [] 

    @classmethod
    def get_concrete_classes(cls):
        return list(cls._subclasses)

    def __init_subclass__(cls):
        BaseClassA._subclasses.append(cls)


class ConcreteClassA1(BaseClassA):
    pass  # no magic-related code here


class ConcreteClassA2(BaseClassA):
    pass  # no magic-related code here


print(BaseClassA.get_concrete_classes())

【讨论】:

【参考方案4】:

如果您的子类未定义 __init__ 或调用其父类的 __init__,则使用装饰器的另一种方法:

def lister(cls):
    cls.classes = list()
    cls._init = cls.__init__
    def init(self, *args, **kwargs):
        cls = self.__class__
        if cls not in cls.classes:
            cls.classes.append(cls)
        cls._init(self, *args, **kwargs)
    cls.__init__ = init
    @classmethod
    def getclasses(cls):
        return cls.classes
    cls.getclasses = getclasses
    return cls

@lister
class A(object): pass

class B(A): pass

class C(A):
    def __init__(self):
        super(C, self).__init__()

b = B()
c = C()
c2 = C()
print 'Classes:', c.getclasses()

无论基类是否定义__init__,它都会起作用。

【讨论】:

'init' 限制是完全可以接受的;但是,请注意,在创建第一个实例之前,不会“注册”具体类。这不适合我想根据可用的具体类来选择要实例化的类的场景。但是,当您只想注册实际上已实例化的类时,这非常有用。 没有考虑您的用例是什么。另外,回过头来补充一下,使用__new__ 而不是__init__ 可能会更好,因为如果不调用父类'__new__,它可能不太可能被覆盖。

以上是关于Python:如何在创建时向父类注册所有子类的主要内容,如果未能解决你的问题,请参考以下文章

关于常见继承的几种方法

java 父类如何在运行期动态获取子类类名

Vue.js - 如何在数据对象更改时向父组件发出?

javaScript基础

如何为父类构造函数提供值,其中父构造函数的参数多于c#中static static main的子类; [关闭]

父类对象与子类对象相互转化的条件是啥?如何实现它们的相互转化?