2009-08-31 73 views
2

由於python沒有常量的概念,如果更新'常量'屬性,是否有可能引發異常?怎麼樣?在python中更新'常量'屬性引發異常

class MyClass(): 
    CLASS_CONSTANT = 'This is a constant' 
    var = 'This is a not a constant, can be updated' 

#this should raise an exception  
MyClass.CLASS_CONSTANT = 'No, this cannot be updated, will raise an exception' 

#this should not raise an exception  
MyClass.var = 'updating this is fine' 

#this also should raise an exception  
MyClass().CLASS_CONSTANT = 'No, this cannot be updated, will raise an exception' 

#this should not raise an exception  
MyClass().var = 'updating this is fine' 

任何嘗試將CLASS_CONSTANT更改爲類屬性或實例屬性都會引發異常。

將var更改爲類屬性或實例屬性不應引發異常。

+0

亞歷克斯·馬爾泰利以及螞蟻阿斯瑪的兩個解決方案的工作。我選擇了Alex Martelli的解決方案,因爲它可以在子類中使用,但是一些開發人員也可能更喜歡螞蟻Aasma的風格。 – e70 2009-08-31 23:17:33

回答

3

每類自定義__setattr__(例如,作爲例證在我的舊的食譜, @ ainab的答案指向以及其他答案),僅用於停止分配到INSTANCE屬性而不是CLASS屬性。所以,現有的答案都不會滿足您的要求。

如果你要的其實正是你想要什麼,你可以求助於定製元類和描述符,如一些混合:

class const(object): 
    def __init__(self, val): self.val = val 
    def __get__(self, *_): return self.val 
    def __set__(self, *_): raise TypeError("Can't reset const!") 

class mcl(type): 
    def __init__(cls, *a, **k): 
    mkl = cls.__class__ 
    class spec(mkl): pass 
    for n, v in vars(cls).items(): 
     if isinstance(v, const): 
     setattr(spec, n, v) 
    spec.__name__ = mkl.__name__ 
    cls.__class__ = spec 

class with_const: 
    __metaclass__ = mcl 

class foo(with_const): 
    CLASS_CONSTANT = const('this is a constant') 

print foo().CLASS_CONSTANT 
print foo.CLASS_CONSTANT 
foo.CLASS_CONSTANT = 'Oops!' 
print foo.CLASS_CONSTANT 

這是非常先進的東西,所以你可能更喜歡簡單__setattr__方法建議在其他答案中,儘管它不符合你所說的要求(即,你可能會合理地選擇削弱你的要求,以獲得簡單;-)。但是這裏的技術可能仍然很有趣:自定義描述符類型const是另一種方式(恕我直言遠比在每個需要一些常量的類中重寫__setattr__更好,並且使所有屬性常量而不是挑選和選擇...)來阻塞賦值到一個實例屬性;代碼的其餘部分是關於自定義元類創建自己的獨特的每類子元類,以便充分利用所述自定義描述符並實現您特別要求的確切功能。

+0

完美!我絕對喜歡你對py問題的回答。我希望SO有一個邀請功能,所以我可以爲你解答我的py問題。謝謝。 – e70 2009-08-31 21:44:17

1

開始閱讀本:

http://docs.python.org/reference/datamodel.html#customizing-attribute-access

你基本上寫的__setattr__拋出自己的版本某些屬性的例外情況,但不是其他情況。

+0

它仍然可以通過訪問:'MyClass.CLASS_CONSTANT'來更新屬性。我認爲這是OP的問題。 – SilentGhost 2009-08-31 18:39:10

+1

「可能」與OP發佈的具體示例不同。除非程序員是惡意的反社會人士,否則「可能」不是問題。它被記錄爲常量;在單元測試之外捕捉誠實的錯誤是唯一的目的。 – 2009-08-31 18:46:09

2

你可以做這樣的事情: (從http://www.siafoo.net/snippet/108

class Constants: 
    # A constant variable 
    foo = 1337 

    def __setattr__(self, attr, value): 
    if hasattr(self, attr): 
     raise ValueError, 'Attribute %s already has a value and so cannot be written to' % attr 
    self.__dict__[attr] = value 

然後使用它是這樣的:

>>> const = Constants() 
>>> const.test1 = 42 
>>> const.test1 
42 
>>> const.test1 = 43 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 4, in __setattr__ 
ValueError: Attribute test1 already has a value and so cannot be written to 
>>> const.test1 
42 
+0

'>>> Constants.foo = 45; >>> Constants.foo 45' – SilentGhost 2009-08-31 18:40:00

+0

是的,顯然,類__setattr__只保護對實例屬性的訪問,而不是類屬性。因此,EPiddy建議您基本上需要確保使用類的實例而不是類對象本身的對象。 – Phil 2009-08-31 18:44:42

2

您可以使用元類來實現這一目標:

class ImmutableConstants(type): 
    def __init__(cls, name, bases, dct): 
     type.__init__(cls, name, bases, dct) 

     old_setattr = cls.__setattr__ 
     def __setattr__(self, key, value): 
      cls.assert_attribute_mutable(key) 
      old_setattr(self, key, value) 
     cls.__setattr__ = __setattr__ 

    def __setattr__(self, key, value): 
     self.assert_attribute_mutable(key) 
     type.__setattr__(self, key, value) 

    def assert_attribute_mutable(self, name): 
     if name.isupper(): 
      raise AttributeError('Attribute %s is constant' % name) 

class Foo(object): 
    __metaclass__ = ImmutableConstants 
    CONST = 5 
    class_var = 'foobar' 

Foo.class_var = 'new value' 
Foo.CONST = 42 # raises 

但你確定這是一個真正的問題?你真的意外地在各地設置常量?您可以通過grep -r '\.[A-Z][A-Z0-9_]*\s*=' src/輕鬆找到其中的大部分。

+0

Foo().CONST = 42不會加註? – e70 2009-08-31 20:28:57

+0

是的,你可以覆蓋實例上的常量。如果這是一個問題,元類初始化器可以爲類安裝一個類似的setattr。我沒有試圖完成它,因爲我不明白爲什麼它是必要的。通過靜態分析(例如grep)很容易發現錯誤,並且如果解釋器中沒有適當的沙箱功能,就不可能保證安全。即使使用C擴展名創建常量,也可以用ctypes覆蓋內存位置,或者如果使用外部const服務,則覆蓋服務位置。 – 2009-08-31 21:35:48

+0

我打算選擇'Alex Martelli'的答案,因爲我可以從with_const繼承並使用const()[class foo(with_const):],請參閱他的解決方案以瞭解詳細信息。但我認爲這是一個風格問題,其他一些開發人員可能更喜歡你的風格。我希望你能修正'Foo()。CONST不會在你的答案中提出問題,所以如果有人使用你的解決方案,他們就不會有這個問題。我認爲你的答案有好處,如果你的解決方案解決了這個問題,我想投票給它。 – e70 2009-08-31 21:41:38