Вот по существу гибридная версия @Ignacio Васкес-Абрамс и ответы @ 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'>]
Update
Точно то же самое можно сделать и с помощью метакласса-который имеет то преимущество, что избавляет от необходимости явно украсит каждый подкласс, как показано в моем оригинальном ответе, что показано выше (который вы приняли), однако его все это происходит автоматически. Обратите внимание, что хотя метаклас «__init__()
вызывается для создания каждого подкласса, он только обновляет список subs
, если в нем появляется имя подкласса, поэтому определение базового класса содержимого списка subs
по-прежнему контролирует то, что получает заменен в нем (сохраняя его порядок).
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.register_subclass()
, или нет его на самом деле подкласс Base
(хотя это может быть возможно изменить/исправить.)
я указываю на это по нескольким причинам: во-первых, потому что в ваших комментариях вы сказали, что subs
была «куча классов в списке, некоторые из который может быть его подклассами », и что более важно, потому что это не случай с кодом в моем обновлении, который работает только для подклассов Base
, так как они эффективно «регистрируются» автоматически через метакласс, но оставляют в списке только что остальное. Это можно считать ошибкой или признаком. ;¬)
Плохой дизайн. Вы объединяете фабрику (которая знает о подклассах) с суперклассом (который не должен знать о подклассах). Зачем это делать? Почему бы просто не разделить вещи и сделать вещи проще? –
@ S.Lott: Что делать, если на самом деле это не означает, что нужно знать о своих подклассах как таковых. Он просто должен иметь кучу классов в списке, некоторые из которых могут быть его подклассами. – pafcu
@pafcu: «некоторые из них могут быть его подклассами».Это все еще плохая идея - суперкласс никогда не должен знать о своих подклассах. Это нарушает принцип проектирования OO. Вы больше не можете просто создавать новые подклассы, не изменяя также суперкласс. Вы должны отделить «список классов» от суперкласса. Единственная причина наличия «списка классов» - это создание фабрики. Завод хорош; однако это не особенность суперкласса. –