2010-11-12 175 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是沒有定義失敗。但顯然我不能在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

回答

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

更新

完全一樣的東西也可以用做一個元類,它不過它有它消除了如在頭頂顯示(你接受)我原來的答案,需要明確地裝點每個子類的優勢,這一切都是自動發生的。請注意,即使元類'__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

編輯補充信息:由於訂單的增加要求的我完全重新設計我的答案。我也使用了一個類裝飾器,這個在這裏首先由@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

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

對不起,忘了指定順序是重要的(這是一個「首選」類型的列表)。這取決於代碼的順序是非常危險的。我想我可以添加一個參數給addsub,但是。儘管Base的內容仍然存在問題。潛艇遍佈各處。 – pafcu 2010-11-12 07:39:47

0
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

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

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

該OP特別提到他/她爲什麼尋找另一種方式來做到這一點。 – martineau 2013-01-04 21:03:26

+0

沒有一個很好的理由,不必要地使事情變得複雜是我能想到的最反pythonic行爲:-) – 2016-02-26 20:28:30

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.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爲最新版本(當然寫下適當的子類)。