2017-02-25 62 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