类的前向声明?

类的前向声明?

问题描述:

我有一些类看起来像这样:类的前向声明?

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是没有定义失败。但显然我不能在Base之前放置子类。有没有办法在Python中转发声明类?我想与isinstance一起工作,因此subs中的类型实际上必须与后面声明的子类相同,它们具有相同的名称和其他属性是不够的。

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

编辑:约为了

+4

这是一个糟糕的设计。你正在将一个工厂(它知道子类)与超类(不需要知道子类)混为一谈。为什么要这样做?为什么不简单地分离事物并使事情更简单? – 2010-11-12 11:03:38

+1

@ S.Lott:如果它本身并不需要知道它的子类本身。它只需要在列表中有一堆类,其中一些可能是它的子类。 – pafcu 2010-11-12 11:44:04

+2

@pafcu:“其中一些可能是它的子类”。这仍然是一个坏主意 - 超类不应该知道它的子类。这样做违反了面向对象设计的原则。如果不更改超类,则不能再简单地创建新的子类。你必须从超类中分离出“类的列表”。拥有“班级列表”的唯一原因是创建工厂。工厂是一件好事;但它不是超类的特征。 – 2010-11-12 13:13:58

这里的本质@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'>] 

更新

完全一样的东西也可以用做一个元类,它不过它有它消除了如在头顶显示(你接受)我原来的答案,需要明确地装点每个子类的优势,这一切都是自动发生的。请注意,即使元类'__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子类,因为它们实际上通过元类自动“注册” - 但会在列表中单独留下任何其他内容。这可以被视为一个错误或功能。​​

编辑补充信息:由于订单的增加要求的我完全重新设计我的答案。我也使用了一个类装饰器,这个在这里首先由@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是Base类的一个属性,所以我宁愿在那里定义它,而不是将它分散到几个类中。 – pafcu 2010-11-12 07:36:07

+0

是的,如果有人没有意识到阶级秩序很重要(通常不会),代码就会以有趣的方式突破。例如,当意外改变订单时很容易。重构或清理代码。 – pafcu 2010-11-12 07:46:26

+0

我还没有看到它**应该**的问题。也许你应该考虑:a)在生成和维护秩序方面更加明确,或者b)不依赖于秩序。为什么它很重要? – knitti 2010-11-12 07:59:38

写一个装饰,它添加到注册表中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

对不起,忘了指定顺序是重要的(这是一个“首选”类型的列表)。这取决于代码的顺序是非常危险的。我想我可以添加一个参数给addsub,但是。尽管Base的内容仍然存在问题。潜艇遍布各处。 – pafcu 2010-11-12 07:39:47

class Foo: 
    pass 

class Bar: 
    pass 

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

这在问题中被排除了。 Foo的定义是分裂的。 – Oddthinking 2010-11-12 07:35:45

+0

不,这不是... – 2010-11-12 07:36:54

我也只是定义了子类为字符串,并有必然的装饰与他们的名字的类更换琴弦。我还会在元类上定义装饰器,因为我认为这更符合目标:我们修改类行为,就像通过修改类修改对象行为一样,通过修改元类来修改类行为。

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 2010-11-12 09:13:46

+0

在这里使用元类有什么意义? – martineau 2010-11-13 00:01:12

+0

@martineau,重点是我们正在改变班级的行为。通过改变类本身来做到这一点本质上是猴子修补一个对象。改变类的理由是改变其实例的行为 - 而不是改变自己的行为。你通过改变_it_是一个实例的类,即它的元类来做到这一点。我们不再用C++编程,类也是对象:)另外,我删除了对你答案的评论。出于某种原因,我并没有真正看到针对我的评论。 – aaronasterling 2010-11-13 08:39:59

我很确定这应该适合你。之后只需指定依赖的类属性。 这也不复杂。

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

该OP特别提到他/她为什么寻找另一种方式来做到这一点。 – martineau 2013-01-04 21:03:26

+0

没有一个很好的理由,不必要地使事情变得复杂是我能想到的最反pythonic行为:-) – 2016-02-26 20:28:30

没有办法直接宣布在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.subsstr值,并使用class decorator来代替实际的亚类(这可以是在基类方法或函数 - I显示功能版本,但我可能会使用方法版本)。

- 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.subsstr值,并且有一个使用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为最新版本(当然写下适当的子类)。