2011-05-19 132 views
31

我有一個子類,我想它而不是包括一個類屬性存在於基類。Python:刪除子類中的類屬性

我嘗試這樣做,但它不工作:

>>> class A(object): 
...  x = 5 
>>> class B(A): 
...  del x 
Traceback (most recent call last): 
    File "<pyshell#1>", line 1, in <module> 
    class B(A): 
    File "<pyshell#1>", line 2, in B 
    del x 
NameError: name 'x' is not defined 

我怎樣才能做到這一點?

+14

這打破了[LSP](http://en.wikipedia.org/wiki/Liskov_substitution_principle)。你爲什麼想這樣做? – 2011-05-19 10:22:50

+1

您想要刪除的'B'類'x'類變量確實存儲在'A .__ dict__'中,所以如果您設法刪除它,您也會刪除'A.x'。因此,最接近的是_hiding_'A.x',通過給'B'一個'x'類變量,就像@Keith在他的回答中所表明的那樣。 – 2011-05-19 11:08:06

+1

@JBernardo:LSP和靜態類型之間沒有太多聯繫。 LSP是一個合理的原則,因爲它使得類層次結構以可預測和一致的方式運行。這同樣適用於靜態和動態類型的語言,不管它們是否提供違反LSP的手段。 – 2011-05-19 16:29:46

回答

7

仔細想想你爲什麼要這麼做;你可能不會。考慮不讓B從A繼承A.

子類化的想法是專門化一個對象。具體而言,一類的兒童應在父類的有效實例:

>>> class foo(dict): pass 
>>> isinstance(foo(), dict) 
... True 

如果實施該行爲(例如帶x = property(lambda: AttributeError)),你打破了子類的概念,這是不好的。

+18

我通常討厭非答案減少到「不要做這個真棒和有趣的事情,因爲我是一個負責任的成年人...... !」這也不例外。雖然保留[Liskov替代品](https://en.wikipedia.org/wiki/Liskov_substitution_principle)對於理智的發展至關重要,但每條規則都打算被打破。我們都是大人,在這裏。 **謝謝。** – 2016-02-02 05:56:32

+0

我一般同意@CecilCurry,因爲我之前遇到過類似的答案,我討厭他們。然而,在這種特殊情況下,我會說刪除一個父屬性首先不是真棒和有趣的,所以提醒[LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle)不是無稽之談。我最終提出了這個答案和CecilCurry的評論。 – RayLuo 2016-10-22 01:41:44

+0

我找到答案指出廣泛接受的編程原理非常有益,因爲我正在學習。我也知道每條規則都有一定的靈活性,所以要把它們作爲指導方針。 – mehmet 2016-11-04 14:20:45

16

您不需要刪除它。只需重寫它。

class B(A): 
    x = None 

或根本不參考它。

或考慮一個不同的設計(實例屬性?)。

+1

+1,因爲它回答了問題,並建議設計更改。 – FlipMcF 2013-10-04 15:36:19

+3

**這根本沒有回答這個問題。**將一個屬性設置爲'None'並不會刪除該屬性,這應該是相當明顯的。刪除屬性是'del'和'delattr()'做的。 [Bhushan](https://stackoverflow.com/users/1594534/bhushan)的簡潔[回覆](https://stackoverflow.com/a/15920132/2809027)似乎是此問題的唯一實際答案。 – 2016-02-02 06:02:09

+0

@CecilCurry更深層的問題是爲什麼OP認爲他需要刪除它?無論如何,這是一個糟糕的設計。 – Keith 2016-02-06 19:17:27

6

也許你可以將x設置爲property,並在有人試圖訪問它時引發AttributeError。

>>> class C: 
     x = 5 

>>> class D(C): 
     def foo(self): 
      raise AttributeError 
     x = property(foo) 

>>> d = D() 
>>> print(d.x) 
File "<pyshell#17>", line 3, in foo 
raise AttributeError 
AttributeError 
+0

'AttributeError'會更一致。 – 2011-05-19 12:18:43

+0

你說得對。編輯。 – JBernardo 2011-05-19 12:21:35

+0

這是目前僅*實際工作的解決方案。當然,除了「僅刪除」子類*實例*上的屬性,而不是子類本身。 – drdaeman 2017-05-05 21:47:42

5

我也有同樣的問題,我想我有一個合法的理由來刪除子類中的類屬性:我的超類(稱爲A)有一個只讀屬性,提供了屬性,但在我的子類(稱爲B)中,該屬性是一個讀/寫實例變量。我發現Python調用了屬性函數,即使我認爲實例變量應該重寫它。我可以創建一個單獨的getter函數來訪問底層屬性,但這看起來像是一個不必要和不雅的界面名稱空間混亂(如果真的很重要)。

事實證明,答案是用A的原始公共屬性創建一個新的抽象超類(稱爲S),並且A和B從S派生出來。由於Python有鴨子打字,它並不真的因爲B不擴展A,我仍然可以在相同的地方使用它們,因爲它們隱含地實現了相同的接口。

32

您可以使用delattr(class, field_name)從類定義中刪除它。

+3

我不是繼承 - 我從我的測試中的設置類中刪除一個設置,以檢查正確的錯誤處理 - 所以這正是我需要的! – sage 2013-12-27 21:12:06

+0

有一個完整的代碼示例會很有幫助。這可以填寫嗎?我不確定代碼中的內容。 – broccoli2000 2016-09-07 19:34:26

0

試圖做到這一點可能是一個壞主意,但......

這似乎並沒有被通過,因爲如何查找B.x作品由默認的「正確」的繼承做到這一點。當得到B.xx首先在B中查找,如果沒有找到,則在A中搜索,但另一方面在設置或刪除B.x時只搜索B。因此,例如

>>> class A: 
>>>  x = 5 

>>> class B(A): 
>>> pass 

>>> B.x 
5 

>>> del B.x 
Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
AttributeError: class B has no attribute 'x' 

>>> B.x = 6 
>>> B.x 
6 

>>> del B.x 
>>> B.x 
5 

在這裏我們看到,首先,我們似乎不能夠刪除B.x,因爲它不存在(A.x存在且得到什麼服務,當你評估B.x)。然而,通過將B.x設置爲6,B.x將存在,它可以通過B.x檢索並且被del B.x刪除,由此它不再存在,所以之後A.x將再次作爲對B.x的響應。

可以,另一方面做的是使用元類,使B.x提高AttributeError

class NoX(type): 
    @property 
    def x(self): 
     raise AttributeError("We don't like X") 

class A(object): 
    x = [42] 

class B(A, metaclass=NoX): 
    pass 

print(A.x) 
print(B.x) 

現在,當然純粹主義者可能會嚷嚷,這打破了LSP,但它不是那麼簡單。這一切都歸結爲如果你認爲你通過這樣做創建了一個子類型。 issubclassisinstance方法表示是,但LSP表示否(因爲您從A繼承,許多程序員會認爲「是」)。

的LSP意味着如果BA子類型,然後我們可以使用B每當我們可以使用A,但由於我們不能做到這一點,而這樣做的構建,我們可以得出這樣的結論B其實不是的亞型A,因此不違反LSP。

2

沒有答案爲我工作。

例如delattr(SubClass, "attrname")(或其確切的等價物,del SubClass.attrname)不會「隱藏」父方法,因爲這不是方法解析的工作方式。由於子類不具有attrname,所以它會因AttributeError('attrname',)而失敗。當然,用None替換屬性實際上並不會將其刪除。

讓我們考慮這個基類:

class Spam(object): 
    # Also try with `expect = True` and with a `@property` decorator 
    def expect(self): 
     return "This is pretty much expected" 

我知道只有兩個唯一途徑繼承它,隱藏expect屬性:

  1. 使用描述符類raises AttributeError from __get__。在屬性查找中,將會有一個異常,通常難以區分查找失敗。

    最簡單的方法是聲明一個屬性,提高AttributeError。這實際上就是@JBernardo所建議的。

    class SpanishInquisition(Spam): 
        @property 
        def expect(self): 
         raise AttributeError("Nobody expects the Spanish Inquisition!") 
    
    assert hasattr(Spam, "expect") == True 
    # assert hasattr(SpanishInquisition, "expect") == False # Fails! 
    assert hasattr(SpanishInquisition(), "expect") == False 
    

    然而,這僅適用於實例,而不是類(在hasattr(SpanishInquisition, "expect") == True斷言將被打破)。

    如果你想所有上述說法是成立的,使用此:

    class AttributeHider(object): 
        def __get__(self, instance, owner): 
         raise AttributeError("This is not the attribute you're looking for") 
    
    class SpanishInquisition(Spam): 
        expect = AttributeHider() 
    
    assert hasattr(Spam, "expect") == True 
    assert hasattr(SpanishInquisition, "expect") == False # Works! 
    assert hasattr(SpanishInquisition(), "expect") == False 
    

    我相信這是最優雅的方式,因爲代碼是明確的,通用的,緊湊。當然,真的如果刪除屬性是他們真正想要的,請三思。

  2. 覆蓋屬性查找with __getattribute__ magic method。你可以在一個子類中(或者像下面這個例子中的mixin那樣做,因爲我只想寫一次),這會隱藏子類實例的屬性。如果你想隱藏子類的方法,你需要使用元類。

    class ExpectMethodHider(object): 
        def __getattribute__(self, name): 
         if name == "expect": 
          raise AttributeError("Nobody expects the Spanish Inquisition!") 
         return super().__getattribute__(name) 
    
    class ExpectMethodHidingMetaclass(ExpectMethodHider, type): 
        pass 
    
    # I've used Python 3.x here, thus the syntax. 
    # For Python 2.x use __metaclass__ = ExpectMethodHidingMetaclass 
    class SpanishInquisition(ExpectMethodHider, Spam, 
             metaclass=ExpectMethodHidingMetaclass): 
        pass 
    
    assert hasattr(Spam, "expect") == True 
    assert hasattr(SpanishInquisition, "expect") == False 
    assert hasattr(SpanishInquisition(), "expect") == False 
    

    這看起來比上述方法更差(更詳細的少通用),但可以考慮這種方式爲好。

    注意,這並不特殊( 「魔術」)的方法(例如__len__工作,因爲這些旁路__getproperty__。有關更多詳細信息,請參閱Python文檔的Special Method Lookup部分。如果這是你需要撤消的,只需重寫它並調用object的實現,即可跳過父項。

不用說,這僅適用於「新樣式類」(即從object繼承者),因爲魔術方法和描述符的協議不受支持那裏。希望這些都是過去的事情。