Подтвердить что ты не робот

Форвардное объявление классов?

У меня есть несколько классов, которые выглядят следующим образом:

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

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

4b9b3361

Ответ 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()