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:如何在创建时向父类注册所有子类的主要内容,如果未能解决你的问题,请参考以下文章