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

ni65a41a  于 2023-02-06  发布在  Python
关注(0)|答案(6)|浏览(195)

根据我的理解,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元素。

6ioyuze2

6ioyuze21#

要定义一个具有不可变示例的类,可以执行以下操作:

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

    # Using __slots__ reduces memory usage.
    __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
xzlaal3s

xzlaal3s2#

对象保持不变,即使你改变了对象的属性,而且不,在python中只有很少的不可变对象比如frozenset,但是类不是不可变的。
如果你想要不可变的对象,你必须让它们成为这样。例如,禁止给属性赋新值,在这种情况下会变成新对象。
要实现这一点,可以使用下划线约定:在你的字段前面加上一个“_”--这向其他开发人员表明这个值是私有的,不应该从外部更改。
如果你想要一个类有一个不可更改的“name”字段,你可以使用以下语法:

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

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

当然,_name可以被dev更改,但这违反了可见的契约。

fdbelqdn

fdbelqdn3#

原因在于,为了使这个对象成为可散列的,尽管它是可变的,Python的默认__hash__()方法从它的引用ID计算散列值。
这意味着,如果你改变它的内容或复制引用到另一个名称,哈希值不会改变,但如果你复制到另一个地方或创建另一个对象与相同的内容,那么它的值将不同.
你可以通过重新定义__hash__()方法来改变这种行为,但是你需要确保对象是不可变的,否则你会破坏你的“命名集合”(字典、集合及其子类)。

brtdzjyr

brtdzjyr4#

这不是Python遵循的契约。从文档中可以看出--我在粗体部分加了强调:
object.__hash__(self)由内置函数hash()调用,用于哈希集合成员(包括setfrozensetdict. __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。并且当检查X1 M23 N1 X时也将被正确地识别为不可散列的。

syqv5f0l

syqv5f0l5#

我将填补Christian答案中的知识空白,来自Python官方网站(https://docs.python.org/2/reference/datamodel.html):
包含对可变对象的引用的不可变容器对象的值可以在可变对象的值改变时改变;然而,容器仍然被认为是不可变的,因为它所包含的对象的集合是不能改变的。2所以,不可变性并不完全等同于拥有一个不可改变的值,它更为微妙。
当我看到一个对象A,它的字节数据永远不会改变,它是真正不可变的,字节数据可能包含指向其他可变对象的指针,但这并不意味着对象A是可变的。
在你的例子中,对象驻留在一个内存位置,Python的哈希生成是不透明的,但是如果你使用相同的引用来查看东西,很可能哈希不会改变,即使存储的字节不同。
从严格意义上讲,可变对象甚至都不是可散列的,因此您不应该首先尝试解释散列。
对于您的问题,只需使用collections.namedtuple即可。

wpx232ag

wpx232ag6#

如果你使用的是Python 3.7或更高版本,另一个使类不可变的方法是使用带有frozen=True选项的dataclass,下面是用这种方法重写的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'

相关问题