2017-02-25 19 views
0

根據我的理解,Python用戶定義的類實例在默認情況下是不可變的。不可變對象不會改變它們的散列值,它們可以用作字典鍵和元素。Python 3用戶定義的不可變類對象

我有下面的代碼片段。

class Person(object): 
    def __init__(self, name, age): 
     self.name=name 
     self.age=age 

現在,我將實例化Person類並創建一個對象並打印它的散列值。

jane = Person('Jane', 29) 
print(jane.__hash__()) 
-9223371933914849101 

現在,我將改變jane對象並打印它的散列值。

jane.age = 33 
print(jane.__hash__()) 
-9223371933914849101 

我的問題是,即使jane對象是可變的,爲什麼它的哈希值沒有改變?

另外,我可以使用可變jane對象作爲dict鍵和set元素。

+1

「按我的理解,Python的用戶定義的類實例是默認不可改變」 - 相反,用戶定義類的實例默認是可變的,並且試圖使它們不可變是相當混亂的。 – user2357112

+0

@ user2357112可以猴子補丁類,所以我很確定類實例是可變的。請參閱http://stackoverflow.com/questions/5626193/what-is-a-monkey-patch – Mai

+0

@Mai:當然,你可以猴子補丁類,但是否這種計數作爲突變他們的實例是爭論。在任何情況下,您都可以通過使用Cython編寫它們或直接使用C API來獲得大多數不可猴子可修補的類,並且即使沒有通過從內置類繼承來將C帶入圖片也可以獲得大多數不可變的實例使用不可變實例並設置__slots__ =()來禁用實例__dict__'的創建。 – user2357112

回答

0

即使您要更改對象的屬性,該對象仍然是相同的。 不,在python中只有很少的不可變對象 - 例如,frozenset。但類不是不可變的。

如果你想要不可變的對象,你必須讓它們如此。例如。在這種情況下,禁止爲屬性分配新的值會變成新的對象。

要實現這一點,您可以使用下劃線約定:在字段前添加一個「_」 - 這表示其他開發者指出該值是私有的,不應該從外部改變。

如果你想與不可以改變的「名稱」一類的字段,你可以使用這個語法:

class test(object): 
    def __init__(name): 
     self._name = name 

    @property 
    def name(self): 
     return self._name 

當然,_name可通過開發來改變,但是,打破了可見的合同。

0

未在合同Python的推移從the docs - 強調通過我的粗體部分添加:

object.__hash__(self)通過內置函數hash()和雜亂的集合成員 操作,包括set調用, frozenset, 和dict. __hash__()應返回一個整數。 唯一需要的 屬性是比較相等的對象具有相同的散列值; 建議將 對象的組件的哈希值混合在一起,這些對象也可以通過將對象與 合併爲一個元組並對元組進行哈希處理來對比對象。例如:

def __hash__(self): 
    return hash((self.name, self.nick, self.color)) Note hash() truncates 

而一些更相關的信息:

如果一個類沒有定義__eq__()方法不應該限定__hash__()操作 任;如果它定義了__eq__()而不是__hash__(),則其實例 不可用作可哈希集合中的項目。如果一個類定義 可變對象,並實現了__eq__()方法,它不應該 實施__hash__(),因爲哈希的集合 的實現需要一個關鍵的哈希值是不可變的(如果對象的散列 值的變化,這將是錯誤的哈希桶)。

而且,你的問題的核心:

用戶定義的類有__eq__()__hash__()方法默認; 與他們,所有對象比較不相等(除了與他們自己)和 x.__hash__()返回一個適當的值,使x == y暗示 這兩個x是y和hash(x) == hash(y)

重寫__eq__()並沒有定義__hash__()將 有其__hash__()隱含設置爲None類。當一個類的方法__hash__()None,該類的實例將當程序試圖以檢索它們的散列值提高的適當 TypeError,和 還將檢查 isinstance(obj, collections.Hashable)時正確地識別爲unhashable。

0

我會填補基督教答案中的知識空白。從Python的官方網站(https://docs.python.org/2/reference/datamodel.html):

包含參考 到一個可變對象可以改變,當後者的值被改變不可變容器對象的數值;但是容器仍然被認爲是不可變的,因爲它所包含的對象集合是不能改變的。所以,不變性 並非嚴格等同於具有不可更改的值,而是更爲微妙的 。

當我看到一個對象A的字節數據永遠不會改變,這是真正不可變的。字節數據可能包含指向其他可變對象的指針,但這並不意味着對象A是可變的。

就你而言,該對象駐留在內存位置。 Python的散列生成是不透明的。但是,如果您使用相同的參考來查看事物,即使存儲的字節不同,最有可能的是哈希也不會更改。

從嚴格意義上說,可變對象是不可散列的,所以你不應該嘗試首先解釋散列。

對於你的問題,只需使用collections.namedtuple來代替。

0

原因是,儘管事實上它是可變的,但是Python的默認__hash __()方法會從它的引用ID中計算出哈希值。

這意味着如果您更改其內容或將引用複製到另一個名稱,則散列值不會更改,但是如果將其複製到另一個地方或創建具有相同內容的另一個對象,則它的值將爲不同。

您可以通過重新定義__hash __()方法來更改該行爲,但需要確保該對象不可變,否則將破壞「命名集合」(字典,設置&的子類)。

0

要定義不變的情況下,一類,你可以做這樣的事情:

class Person: 
    """Immutable person class""" 

    # Using __slots__ reduces memory usage. 
    # If __slots__ doesn't include __dict__, new attributes cannot be added. 
    # This is not always desirable, e.g. it you want to subclass Person. 
    __slots__ = ('name', 'age') 

    def __init__(self, name, age): 
     """Create a Person instance. 

     Arguments: 
      name (str): Name of the person. 
      age: Age of the person. 
     """ 
     # Parameter validation. This shows how to do this, 
     # but you don't always want to be this inflexibe. 
     if not isinstance(name, str): 
      raise ValueError("'name' must be a string") 
     # Use super to set around __setattr__ definition 
     super(Person, self).__setattr__('name', name) 
     super(Person, self).__setattr__('age', int(age)) 

    def __setattr__(self, name, value): 
     """Prevent modification of attributes.""" 
     raise AttributeError('Persons cannot be modified') 

    def __repr__(self): 
     """Create a string representation of the Person. 
     You should always have at least __repr__ or __str__ 
     for interactive use. 
     """ 
     template = "<Person(name='{}', age={})>" 
     return template.format(self.name, self.age) 

測試:

In [2]: test = Person('S. Eggs', '42') 

In [3]: str(test) 
Out[3]: "<Person(name='S. Eggs', age=42)>" 

In [4]: test.name 
Out[4]: 'S. Eggs' 

In [5]: test.age 
Out[5]: 42 

In [6]: test.name = 'foo' 
--------------------------------------------------------------------------- 
AttributeError       Traceback (most recent call last) 
<ipython-input-6-1d0482a5f50c> in <module>() 
----> 1 test.name = 'foo' 

<ipython-input-1-efe979350b7b> in __setattr__(self, name, value) 
    24  def __setattr__(self, name, value): 
    25   """Prevent modification of attributes.""" 
---> 26   raise AttributeError('Persons cannot be modified') 
    27 
    28  def __repr__(self): 

AttributeError: Persons cannot be modified