Python 3用户定义了不可变类对象

时间:2017-02-25 06:49:53

标签: python python-3.x

根据我的理解,Python用户定义的类实例默认是不可变的。不可变对象不会更改其哈希值,它们可以用作字典键和设置元素。

我有以下代码段。

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元素。

6 个答案:

答案 0 :(得分:2)

要定义具有不可变实例的类,您可以执行以下操作:

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

答案 1 :(得分:0)

即使您要更改对象的属性,对象也保持不变。 不,python中只有很少的不可变对象 - 例如冻结。但是课程不是一成不变的。

如果你想要不可变对象,你必须这样做。例如。在这种情况下,禁止为属性分配新值正在转换新对象。

要实现此目的,您可以使用下划线约定:在字段前加上“_” - 这表示其他开发人员的值是私有的,不应该从外部更改。

如果您想要一个具有不可更改的“名称”字段的类,您可以使用以下语法:

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

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

当然,_name可以由dev更改,但这会破坏可见的合同。

答案 2 :(得分:0)

这不是Python从the docs开始的合同 - 我在加粗部分上强调的重点:

  

object.__hash__(self)由内置函数hash()调用,用于   散列集合成员的操作,包括setfrozenset,   并且dict. __hash__()应该返回一个整数。 唯一需要的   property是比较相等的对象具有相同的哈希值;   建议将组件的哈希值混合在一起   通过打包它们也可以在对象比较中起作用的对象   进入一个元组并对元组进行哈希处理。例如:

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)

答案 3 :(得分:0)

我将填写克里斯蒂安答案中的知识空白。来自Python的官方网站(https://docs.python.org/2/reference/datamodel.html):

  

包含引用的不可变容器对象的值   当一个可变对象的值改变时,它可以改变;   但是容器仍然被认为是不可变的,因为   它包含的对象集合无法更改。所以,不变性   并不是具有不可改变的价值的严格相同,而是更多   微妙。

当我查看其字节数据永不改变的对象A时,这确实是不可变的。字节数据可能包含指向其他可变对象的指针,但这并不意味着对象A是可变的。

在您的情况下,对象驻留在内存位置。 Python的哈希生成是不透明的。但是如果你正在使用相同的引用来查看事物,那么即使存储的字节不同,哈希也很可能不会发生变化。

从严格意义上讲,可变对象甚至不可用,所以你不应该首先尝试解释哈希。

对于您的问题,请改用collections.namedtuple

答案 4 :(得分:0)

原因是为了使这个对象可以清除,尽管它是可变的,Python的默认__hash __()方法从它的引用ID计算哈希值。

这意味着如果您更改其内容或将引用复制到另一个名称,则哈希值不会更改,但如果您将其复制到其他位置或创建具有相同内容的另一个对象,则它的值将不同

您可以通过重新定义__hash __()方法来更改该行为,但是您需要确保该对象不可变,否则您将破坏“命名集合”(字典,集合及其子类)。

答案 5 :(得分:0)

如果您使用的是 Python 3.7 或更高版本,另一种使类不可变的方法是使用带有 dataclass 选项的 frozen=True。 这是用这种方法重写的 Person 类。

from dataclasses import dataclass


@dataclass(frozen=True)
class Person():
    name: str
    age: int

您可以像在示例中一样实例化此类。

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

但是当您尝试更新 age 属性时,您会得到一个异常,因为该实例是不可变的。

>>> jane.age = 33
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'age'