前进声明课程?

时间:2010-11-12 07:25:36

标签: python

我有一些看起来像这样的课程:

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
...

现在,这会失败,因为Base.subs时未定义Sub1和Sub3。但显然我不能把子类放在Base之前。有没有办法在Python中转发声明类?我想使用isinstance,因此subs中的类型实际上必须与后面声明的子类相同,它们具有相同的名称和其他属性是不够的。

一个解决方法是:定义了子类之后的Base.subs = [Sub3,Sub1] ,但我不喜欢以这种方式拆分我的类。

编辑:添加了有关订单的信息

8 个答案:

答案 0 :(得分:12)

编写一个装饰器,将其添加到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

答案 1 :(得分:12)

这里基本上是@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列表,如果子类的名称出现在其中 - 那么内容的初始基类”定义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子类,因为它们实际上通过元类自动“注册” - 但会在列表中留下任何其他内容单独。这可以被视为错误或功能。 ;¬)

答案 2 :(得分:4)

修改:由于增加了订单要求,我完全重写了我的答案。我还使用了类装饰器,这首先由@Ignacio Vazquez-Abrams使用。

编辑2:代码现已经过测试,有些愚蠢有些纠正


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

答案 3 :(得分: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>]

答案 4 :(得分: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替换实际的子类(这可以是Base上的类方法或者function - 我正在显示函数版本,虽然我可能会使用方法版本。)

- 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保持最新状态(当然也要编写适当的子类)。

答案 5 :(得分:1)

我只是将子类定义为字符串,让不可避免的装饰器用它们命名的类替换字符串。我还要在元类上定义装饰器,因为我认为这更符合目标:我们正在修改类行为,就像你通过修改它的类来修改对象行为一样,你通过修改它的元类来修改类行为。 p>

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

答案 6 :(得分:0)

class Foo:
  pass

class Bar:
  pass

Foo.m = Bar()
Bar.m = Foo()

答案 7 :(得分:0)

有一个解决方法:将引用的虚拟Sub1,Sub3类放在顶部,这用作“转发声明”。执行时,它们将被具有相同名称的实际实现替换。

forward-declaration.py:

class Sub1(): 
    print("Sub1 dummy class called")
    pass
class Sub3():
    print("Sub3 dummy class called")
    pass

class Base:
  subs = [Sub3, Sub1]
  print("Base class called")

class Sub1(Base): 
    print("Sub1 class called")
    def __init__(self):
        print("Sub1:__init__ called")
    pass
class Sub2(Base):
    def __init__(self):
        print("Sub2:__init__ called")
    pass
class Sub3(Base):
    def __init__(self):
        print("Sub3:__init__ called")
    pass

sub_1 = Sub1()
sub_2 = Sub2()
sub_3 = Sub3()

print(Base.subs)

python forward-declaration.py

Sub1 dummy class called
Sub3 dummy class called
Base class called
Sub1 class called
Sub1:__init__ called
Sub2:__init__ called
Sub3:__init__ called
[<class '__main__.Sub3'>, <class '__main__.Sub1'>]

注意:上面的方法在mypy或pylint静态检查上失败,但是可以正常工作