Форвардное объявление классов?
У меня есть несколько классов, которые выглядят следующим образом:
class Base:
subs = [Sub3,Sub1]
# Note that this is NOT a list of all subclasses!
# Order is also important
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
...
Теперь это не удается, потому что Sub1 и Sub3 не определены, когда Base.subs. Но, очевидно, я не могу поставить подклассы перед базой. Есть ли способ переназначить классы в Python? Я хочу работать с isinstance
, поэтому типы в subs фактически должны быть такими же, как и более поздние объявленные подклассы, недостаточно, чтобы они имели одно и то же имя и другие свойства.
Один способ решения: Base.subs = [Sub3,Sub1]
после определения подклассов, но мне не нравится разделять мой класс таким образом.
Изменить: Добавлена информация о заказе
Ответы
Ответ 1
Это гибридная версия ответов @Ignacio Vazquez-Abrams и @aaronasterling, которая сохраняет порядок подклассов в списке. Изначально имена подклассов помещаются вручную в список subs
в желаемом порядке. Затем, когда каждый подкласс определен, декоратор класса вызывает замену соответствующей строки фактическому подтипу.
class Base:
@classmethod
def registersub(cls, subcls):
""" Decorator to register subclass types. """
# replace occurrence(s) of subclass name in 'subs' with the subclass itself
while subcls.__name__ in cls.subs:
cls.subs[cls.subs.index(subcls.__name__)] = subcls
return cls
subs = ['Sub3','Sub1'] # some subclass names in some special order
@Base.registersub
class Sub1(Base): pass
@Base.registersub
class Sub2(Base): pass
@Base.registersub
class Sub3(Base): pass
print Base.subs
# [<class __main__.Sub3>, <class __main__.Sub1>]
Важно отметить, что существует, возможно, тонкая разница между моим первоначальным ответом выше и обновлением, которое следует ниже, а именно: то, что первое будет работать с любым именем класса, зарегистрированным в @Base.registersub
, независимо от того, а не его подкласс Base
.
Я указываю это по двум причинам. Во-первых, потому что в ваших комментариях вы сказали, что subs
был "связкой классов в списке, некоторые из которых могут быть его подклассами", и что более важно, потому что это не так с кодом в моем обновлении, который работает только для Base
, поскольку они эффективно "регистрируются" автоматически через метакласс, но оставляют в списке только что-нибудь другое.
Обновить
Точно то же самое можно сделать и с помощью метакласса, который имеет то преимущество, что он устраняет необходимость явно украшать каждый подкласс, как показано в принятом ответе выше, и вместо этого делает это автоматически. Обратите внимание: несмотря на то, что метакласс __init__()
вызывается для создания каждого подкласса, он обновляет список subs
, если это имя подкласса появляется в нем, поэтому исходное определение базового класса содержимого subs
все еще контролирует что попадает в него и в каком порядке.
class BaseMeta(type):
def __init__(cls, name, bases, classdict):
if classdict.get('__metaclass__') is not BaseMeta: # subclass?
# replace any occurrences of this subclass name
# in Base class 'subs' list with the subclass itself
while name in cls.subs:
cls.subs[cls.subs.index(name)] = cls
type.__init__(cls, name, bases, classdict)
class Base:
__metaclass__ = BaseMeta
subs = ['Sub3','Sub1'] # some subclass names in some special order
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
print Base.subs
# [<class '__main__.Sub3'>, <class '__main__.Sub1'>]
Ответ 2
Напишите декоратор, который добавляет его в реестр в Base
.
class Base(object):
subs = []
@classmethod
def addsub(cls, scls):
cls.subs.append(scls)
...
@Base.addsub
class Sub1(Base):
pass
class Sub2(Base):
pass
@Base.addsub
class Sub3(Base):
pass
Ответ 3
Изменить: Из-за дополнительного требования заказа я полностью переработал свой ответ. Я также использую декоратор класса, который сначала использовал здесь @Ignacio Vazquez-Abrams.
Edit2: проверен код и немного исправлена некоторая глупость
class Base(object):
subs = []
@classmethod
def addsub(cls, before=None):
def inner(subclass):
if before and before in cls.subs:
cls.subs.insert(cls.subs.index(before), subclass)
else:
cls.subs.append(subclass)
return subclass
return inner
@Base.addsub()
class Sub1(Base):
pass
class Sub2(Base):
pass
@Base.addsub(before=Sub1)
class Sub3(Base):
pass
Ответ 4
Я уверен, что это сработает для вас. После этого просто назначьте атрибут зависимого класса.
Это также намного менее сложно.
class Base:pass
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
Base.subs = [Sub3,Sub1]
print(Sub1.subs)
#[<class __main__.Sub3 at 0x0282B2D0>, <class __main__.Sub1 at 0x01C79810>]
Ответ 5
Невозможно напрямую объявить форвардные ссылки в Python, но есть несколько обходных путей, некоторые из которых разумны:
1) Добавьте подклассы вручную после их определения.
- Pros: easy to do; Base.subs is updated in one place
- Cons: easy to forget (plus you don't want to do it this way)
Пример:
class Base(object):
pass
class Sub1(Base):
pass
class Sub2(Base):
pass
class Sub3(Base):
pass
Base.subs = [sub3, sub1]
2) Создайте Base.subs
с str
значениями и используйте class decorator
для подстановки фактических подклассов (это может быть метод класса на базе или функция - я показывая версию функции, хотя я бы, вероятно, использовал версию метода).
- Pros: easy to do
- Cons: somewhat easy to forget;
Пример:
def register_with_Base(cls):
name = cls.__name__
index = Base.subs.index(name)
Base.subs[index] = cls
return cls
class Base(object):
subs = ['Sub3', 'Sub1']
@register_with_Base
class Sub1(Base):
pass
class Sub2(Base):
pass
@register_with_Base
class Sub3(Base):
pass
3) Создайте Base.subs
с str
значениями и используйте метод Base.subs
для подстановки.
- Pros: no extra work in decorators, no forgetting to update `subs` later
- Cons: small amount of extra work when accessing `subs`
Пример:
class Base(object):
subs = ['Sub3', 'Sub1']
def select_sub(self, criteria):
for sub in self.subs:
sub = globals()[sub]
if #sub matches criteria#:
break
else:
# use a default, raise an exception, whatever
# use sub, which is the class defined below
class Sub1(Base):
pass
class Sub2(Base):
pass
class Sub3(Base):
pass
Я бы использовал параметр 3 сам, так как он сохраняет функциональность и все данные в одном месте. Единственное, что вам нужно сделать, это сохранить subs
в актуальном состоянии (и, конечно же, написать соответствующие подклассы).
Ответ 6
Я бы просто определил подклассы как строки и имел неизбежный декоратор, заменив строки на классы, которые они называют. Я бы также определил декоратор на метаклассе, потому что я думаю, что это больше соответствует цели: мы модифицируем поведение класса и так же, как вы изменяете поведение объекта, изменяя его класс, вы изменяете поведение класса, изменяя его метакласс.
class BaseMeta(type):
def register(cls, subcls):
try:
i = cls.subs.index(subcls.__name__)
except ValueError:
pass
else:
cls.subs[i] = subcls
finally:
return cls
class Base(object):
__metaclass__ = BaseMeta
subs = ['Sub3', 'Sub1']
@Base.register
class Sub1(Base): pass
@Base.register
class Sub2(Base): pass
@Base.register
class Sub3(Base): pass
print Base.subs
Выводится:
[<class '__main__.Sub3'>, <class '__main__.Sub1'>]
Ответ 7
class Foo:
pass
class Bar:
pass
Foo.m = Bar()
Bar.m = Foo()