这基本上是@Ignacio Vazquez-Abrams' 和@aaronasterling 的答案的混合版本,它保留了列表中子类的顺序。最初所需的子类名称(即字符串)以所需的顺序手动放置在subs 列表中,然后在定义每个子类时,类装饰器会导致相应的字符串替换为实际的子类:
class Base(object): # New-style class (i.e. explicitly derived from object).
@classmethod
def register_subclass(cls, subclass):
""" Class decorator for registering subclasses. """
# Replace any occurrences of the class name in the class' subs list.
# with the class itself.
# Assumes the classes in the list are all subclasses of this one.
# Works because class decorators are called *after* the decorated class'
# initial creation.
while subclass.__name__ in cls.subs:
cls.subs[cls.subs.index(subclass.__name__)] = subclass
return cls # Return modified class.
subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
@Base.register_subclass
class Sub1(Base): pass
@Base.register_subclass
class Sub2(Base): pass
@Base.register_subclass
class Sub3(Base): pass
print('Base.subs: {}'.format(Base.subs))
# Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>]
更新:元类
同样的事情也可以使用元类来完成——它的优点是它消除了显式装饰每个子类的需要,如我上面的原始答案所示(你接受了),但是它使这一切自动发生。
请注意,即使为创建 每个 子类调用元类的 __init__(),它也只会在子类的名称出现在其中时更新 subs 列表——所以初始 Base subs 列表的内容的 class' 定义仍然控制其中被替换的内容(保持其顺序)。
class BaseMeta(type):
def __init__(cls, name, bases, classdict):
if classdict.get('__metaclass__') is not BaseMeta: # Metaclass instance?
# Replace any occurrences of a subclass' name in the class being
# created the class' sub list with the subclass itself.
# Names of classes which aren't direct subclasses will be ignored.
while name in cls.subs:
cls.subs[cls.subs.index(name)] = cls
# Chain to __init__() of the class instance being created after changes.
# Note class instance being defined must be new-style class.
super(BaseMeta, cls).__init__(name, bases, classdict)
# Python 2 metaclass syntax.
class Base(object): # New-style class (derived from built-in object class).
__metaclass__ = BaseMeta
subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
# Python 3 metaclass syntax.
#class Base(metaclass=BaseMeta):
# subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
# Note: No need to manually register the (direct) subclasses.
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
print('Base.subs: {}'.format(Base.subs))
输出:
Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>]
请务必注意,这两个答案之间至少存在一个细微差别——即第一个将与通过 @Base.register_subclass() 注册的 any 类名一起使用,无论它是否真的是Base 的子类(尽管这可能会更改/修复。)
我指出这一点有几个原因:首先,因为在您的 cmets 中,您说 subs 是“列表中的一组类,其中一些可能是它的子类”,更重要的是,因为不是我更新中的代码的情况,它仅适用于Base 子类,因为它们有效地通过元类自动“注册”——但会将其他任何内容单独留在列表中。这可以被认为是一个错误或一个功能。 ;¬)
更新:Python 3.6+
在 Python 3.6 中添加了一个名为 __init_subclass__() 的新 object 方法,它提供了一种更简单的方法来实现也不需要装饰所有子类或定义元类:
#!/usr/bin/env python3.6
class Base:
subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
while cls.__name__ in cls.subs:
cls.subs[cls.subs.index(cls.__name__)] = cls
# Note: No need to manually register the subclasses.
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
print('Base.subs: {}'.format(Base.subs))