鉴于以下示例,
class A(object):
pass
a = A()
a.x = 1
显然a是可变的,然后我把它放在一个集合中,
set([a])
成功了。为什么我可以将像“a”这样的可变对象放入set / dict中?不应该设置/ dict只允许不可变对象,以便它们可以识别对象并避免重复吗?
答案 0 :(得分:12)
Python不测试可变对象,它测试 hashable 对象。
自定义类实例默认为hashable。这很好,因为此类的默认__eq__
实现仅测试实例身份,并且哈希基于相同的信息。
换句话说,改变实例属性的状态并不重要,因为实例的身份无论如何都是不可变的。
只要您实施了考虑实例状态的__hash__
和__eq__
方法,您就可能遇到麻烦并且应该停止改变该状态。只有这样,自定义类实例才不适合存储在字典或集合中。
答案 1 :(得分:4)
从docs要求是它必须是可以清洗的并且可以进行比较:
如果对象具有永不更改的哈希值,则该对象是可清除的 在它的生命周期中(它需要一个哈希()方法),并且可以 与其他对象相比(它需要 eq ()或 cmp ()方法)。 比较相等的Hashable对象必须具有相同的哈希值。
Hashability使对象可用作字典键和集合 member,因为这些数据结构在内部使用哈希值。
所有Python的不可变内置对象都是可清除的,而没有 可变容器(如列表或词典)是。对象 用户定义的类的实例默认情况下是可清除的;他们 所有比较不相等(除了他们自己),他们的哈希值是 他们的id()。
从上一部分可以看出,用户定义的类(重点是我的)默认是可以使用的
docs中没有提到set
的可变性要求:
class set([iterable])class frozenset([iterable])返回一个新的集合或 firset对象,其元素取自iterable。要素 一套必须是可以清洗的。表示集合,内部集合 必须是冷冻对象。如果未指定iterable,则为新的空 返回。)
对于dict
,要求是密钥是可清除的:
映射对象将可哈希值映射到任意对象。映射 是可变对象。目前只有一个标准映射 类型,字典。 (对于其他容器,请参阅内置列表, set和tuple类,以及collections模块。)
答案 2 :(得分:1)
在做了一些更多的研究之后,我能够找出为什么我认为set和dict只允许不可变对象分别作为条目和键,这是不正确的。我认为在这里澄清这一点非常重要,因为我确信有些新手对Python有着与之前相同的疑问。我之前有两个原因让不变性与可持续性相混淆。第一个原因是所有内置的不可变对象(tuple,frozenset ...)都被允许作为set entry或dict键,而所有内置的可变容器对象都不允许。第二个原因实际上是这个问题的来源。我正在读这本书掌握面向对象的Python 。在它解释__hash__
函数的部分中,它引导读者认为不可变性是可持续性的先决条件。
不可变性的定义是针对某个对象,在创建后,您无法更改其现有属性的值或创建新属性。因此它与hashability无关,它要求对象定义其__hash__()
和__eq__()
方法,并且它们的哈希值在其生命周期中是不可变的。实际上,可散列对象的哈希值是不可变的。我想这就是这两个概念经常混淆的原因之一。
答案 3 :(得分:0)
添加成员时id
不会更改。所以没有理由不去工作。
class A(object):
pass
a = A()
print(id(a))
a.x = 1
print(id(a))