2010-11-12 2 views
31

У меня есть несколько классов, глядя, как это:Форвардное объявление классов?

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]после Подклассы определены, но мне не нравится разделять мой класс таким образом.

Edit: Добавлена ​​информация о заказе

+4

Плохой дизайн. Вы объединяете фабрику (которая знает о подклассах) с суперклассом (который не должен знать о подклассах). Зачем это делать? Почему бы просто не разделить вещи и сделать вещи проще? –

+1

@ S.Lott: Что делать, если на самом деле это не означает, что нужно знать о своих подклассах как таковых. Он просто должен иметь кучу классов в списке, некоторые из которых могут быть его подклассами. – pafcu

+2

@pafcu: «некоторые из них могут быть его подклассами».Это все еще плохая идея - суперкласс никогда не должен знать о своих подклассах. Это нарушает принцип проектирования OO. Вы больше не можете просто создавать новые подклассы, не изменяя также суперкласс. Вы должны отделить «список классов» от суперкласса. Единственная причина наличия «списка классов» - это создание фабрики. Завод хорош; однако это не особенность суперкласса. –

ответ

10

Вот по существу гибридная версия @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, так как они эффективно «регистрируются» автоматически через метакласс, но оставляют в списке только что остальное. Это можно считать ошибкой или признаком. ;¬)

3

Edit: Из-за добавленного требования порядка я полностью переработан мой ответ. Я также использую декоратор класса, который сначала использовал здесь @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 

+0

В этом случае трудно определить порядок списка Base.subs. Это будет зависеть от порядка, в котором я пишу код, который довольно подвержен ошибкам. Кроме того, Base.subs является свойством базового класса, поэтому я предпочитаю его определять, а не распространять его на несколько классов. – pafcu

+0

Да, и код разрывается интересным образом, если кто-то не понимает, что имеет порядок в классе (обычно это не так). Легко случайно изменить порядок, когда, например, рефакторинг или очистка кода. – pafcu

+0

Я не видел проблемы, когда это ** должно ** иметь значение. Возможно, вам стоит подумать о том, что a) более явственно проявляется в создании и поддержании порядка или b) вообще не зависит от порядка. Почему это имеет значение? – knitti

11

Написать декоратор, который добавляет его в реестр в 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 
+0

Извините, забыли указать, что порядок важен (это список «предпочтительных» типов). Если это зависит от порядка кода, это довольно опасно. Наверное, я мог бы добавить параметр для addub для этого. Это все еще имеет проблему, хотя содержимое Base.subs определяется по всему месту. – pafcu

0
class Foo: 
    pass 

class Bar: 
    pass 

Foo.m = Bar() 
Bar.m = Foo() 
+0

Это было исключено в вопросе. Определение Foo расщепляется. – Oddthinking

+0

Нет, не это ... –

1

Я бы просто определить подклассы как строки и есть неизбежный декоратор заменить струны с классами, которые они называют. Я бы также определил декоратор на метаклассе, потому что я думаю, что это больше соответствует цели: мы модифицируем поведение класса и так же, как вы изменяете поведение объекта, изменяя его класс, вы изменяете поведение класса, изменяя его метакласс.

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'>] 
+0

Это похоже на действительно интересный подход, потому что я могу указать элементы base.subs в одном месте. Это значительно облегчает изменение при необходимости. – pafcu

+0

В чем смысл использования метакласса? – martineau

+0

@martineau, Дело в том, что мы меняем поведение класса. Выполнение этого путем изменения самого класса - это, по сути, обезглавливание объекта. Причиной изменения класса является изменение поведения его экземпляров, а не изменение его собственного поведения. Вы делаете это, изменяя класс, который _it_ является экземпляром, а именно его метаклассом. Мы больше не программируем на C++, классы тоже объекты :) Кроме того, я удалил свой snarky комментарий к вашему ответу. По какой-то причине я на самом деле не видел комментариев, направленных мне. – aaronasterling

3

Я уверен, что это должно сработать для вас. После этого просто назначьте атрибут зависимого класса. Это также намного менее сложно.

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>] 
+0

ОП конкретно упоминал, почему он/она искал другой способ сделать это. – martineau

+0

Усложнение вещей без необходимости, без уважительной причины, является самым антипитоновым поведением, которое я могу придумать :-) –

3

Там нет никакого способа непосредственно объявить вперед ссылки на 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, чтобы заменить фактические подклассы (это может быть метод класса на базе или функция - Я 'm показывает версию функции, хотя я бы, вероятно, использовал версию метода).

- 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 

) Создание 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 

Я хотел бы использовать вариант сам, как он держит функциональность и данные в одном месте. Единственное, что вам нужно сделать, это сохранить subs в актуальном состоянии (и, конечно же, написать соответствующие подклассы).

Смежные вопросы