2016-05-12 37 views
1

我目前需要部分創建一個Python對象並能夠更新一段時間。雖然,我不能一旦使用該對象作爲字典鍵就能更新它。Python僞不可變對象字段

當然,還有一種解決方案將字段標記爲私有,這對於程序員來說大多是一個警告,我會真正去尋求解決方案。

但是我偶然發現了另一種解決方案,我想知道這是否是一個好主意,或者如果它可能會出現可怕的錯誤。那就是:

class Foo(): 
    def __init__(self, bar): 
     self._bar = bar 
     self._has_been_hashed = False 

    def __hash__(self): 
     self._has_been_hashed = True 
     return self._bar.__hash__() 

    def __eq__(self, other): 
     return self._bar == other._bar 

    def __copy__(self): 
     return Foo(self._bar) 

    def set_bar(self, bar): 
     if self.has_been_hashed: 
      raise FooIsNowImmutable 
     else: 
      self._bar = bar 

一些測試證明它的工作是需要的話,我可以不再使用set_bar一旦我,說,用我的對象作爲字典的鍵。

您認爲如何?這是個好主意嗎?它會反對我嗎?有更容易的方法嗎?這是一種不好的做法嗎?

+0

如果某人只是在您的對象上調用「hash(obj)」,或者在一個集合中使用它,它也將變得不可變。你還好嗎? – BrenBarn

+0

我想它會沒問題,因爲當你檢索一個對象的散列時,你認爲它永遠不會改變。所以你確實認爲它會被鎖定。 –

+0

注意事項:使用「@屬性」會讓這個更自然;命名爲'bar',讓getter變得微不足道('return self._bar'),並且setter會在執行set之前檢查你的「被鎖定」標誌。 – ShadowRanger

回答

2

這樣做有點脆弱,因爲你永遠不知道什麼時候可能會用作字典鍵,或者因爲其他原因可能會調用它。一個對象不應該「知道」它是否被用作字典鍵。如果有代碼可能會引發異常,只是因爲其他代碼將某個對象放在字典中,這會讓人感到困惑。

遵循Python的「顯式優於隱式」的哲學,只需給對象一個名爲.finalize().lock()的方法或其他方法會更安全,這會設置一個標誌,指示該對象是不可變的。您還可以反轉異常提升邏輯,以便__hash__在對象尚未鎖定的情況下引發異常(而不是在對象被散列時引發異常)。

當您準備使對象不可變時,您將會調用.lock()。當你完成任何你需要做的變化時,明確地設置它是不變的,而不是隱含地假設,只要你在字典中使用它,你就完成了變異。

+0

謝謝,lock()函數聽起來像是一種更好的選擇!另外,我只是意識到,如果對象不再被用作關鍵字,那麼可能會成爲問題的是......在這種情況下,無法知道。 –

0

你可以這樣做,但我不確定我會推薦它。你爲什麼在字典中需要它?

它需要更多的對象狀態的認識......想象一個文件對象。你會在字典中加入一個嗎?它必須打開許多功能才能工作,一旦它關閉,你就不能再做它們了。用戶必須在周圍的代碼中知道對象所處的狀態。

對於文件,這是有道理的 - 畢竟,通常不會在大部分程序中保存文件,或者如果您執行,他們有非常明確的init和close代碼;類似的東西對你的對象有意義。特別是如果你有一些接受對象的API,但期望一個不可變的版本,並且其他接受相同對象但希望改變它的其他API ...

我之前使用過鎖定方法,複雜的,只想要初始化的只讀對象,然後確保沒有人搞亂。例如。你從磁盤加載一個(說,英文)字典的副本...它在填充它時必須是可變的,但你不希望任何人不小心修改它,所以鎖定它是一個好主意。如果是一次性鎖定,我只會使用它 - 你鎖定和解鎖的東西看起來像是一場災難。

有兩種解決方案恕我直言,如果你只是想創建一個版本,你可以在可哈希的地方使用。首先是明確地創建一個不可變的副本,當你把它放在一本字典中時 - tuplefrozenset就是這種行爲的例子...如果你想把list放在dict中,你不能,但是你可以創建一個從它第一次,它可以被哈希。創建對象的frozen版本,然後通過查看對象類型非常清楚它是否應該是可變的或不可變的,所以很容易看到它被錯誤使用的情況。

二,如果你真的想要它可以哈希,但需要它是可變的......這實際上是合法的,但實施有點不同。它回到散列的想法...散列用於優化查找和平等。

第一種方法是確保您可以取回對象...您將某些內容放入字典中,並將其哈希值設置爲4 - 在第4列中進行修改。然後對其進行修改。然後你再去查看它,現在它已經達到了9點 - 第9號槽沒有任何東西,或者更糟糕的是,它是一個不同的物體,而且你已經壞了。

其次是平等 - 對於像套的事情,我需要知道我的對象是否已經在那裏。我可以散列,但如果你知道有關散列的任何事情,你仍然需要檢查相等性來檢查散列衝突。

這並不排除支持__hash__是可變的,但它是不尋常的。你需要決定你的物品是什麼使它一樣,即使它是可變的。你需要做的是給每個對象一個唯一的ID。從技術上講,你可能能夠脫身id(self),但類似uuid模塊可能是一個更好的可能性。 UUID4(或技術上來說,UUID4的散列)決定了散列和相等;兩個包含相同UUID4的對象應該是完全相同的對象;具有完全相同數據但不同UUID4的兩個對象將是不同的對象。