Python:为什么我可以将可变对象放入dict或set中?

时间:2015-07-10 12:26:42

标签: python hash immutability

鉴于以下示例,

class A(object):
    pass
a = A()
a.x = 1

显然a是可变的,然后我把它放在一个集合中,

set([a])

成功了。为什么我可以将像“a”这样的可变对象放入set / dict中?不应该设置/ dict只允许不可变对象,以便它们可以识别对象并避免重复吗?

4 个答案:

答案 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))