根据我的理解,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元素。
答案 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()
调用,用于 散列集合成员的操作,包括set
,frozenset
, 并且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'