Python: 为什么我可以将可变对象放在字典或集合中?
Python: why can I put mutable object in a dict or set?
给定以下示例,
class A(object):
pass
a = A()
a.x = 1
很明显a是可变的,然后我把a放到了set里,
set([a])
成功了。为什么我可以将 "a" 之类的可变对象放入 set/dict 中? set/dict 难道不应该只允许不可变对象以便它们可以识别对象并避免重复吗?
Python 不测试 mutable 对象,它测试 hashable 对象。
自定义 class 实例默认可哈希。这很好,因为此类 classes 的默认 __eq__
实现仅测试实例 identity 并且散列基于相同的信息。
换句话说,改变实例属性的状态并不重要,因为实例的身份无论如何都是不可变的。
一旦您实施了考虑实例状态的 __hash__
和 __eq__
方法,您可能会遇到麻烦,应该停止改变该状态。只有这样,自定义 class 实例才不再适合存储在字典或集合中。
添加成员时 id
不会改变。所以没有理由不工作。
class A(object):
pass
a = A()
print(id(a))
a.x = 1
print(id(a))
根据 docs 的要求是它必须是可散列的并且可以比较:
An object is hashable if it has a hash value which never changes
during its lifetime (it needs a hash() method), and can be
compared to other objects (it needs an eq() or cmp() method).
Hashable objects which compare equal must have the same hash value.
Hashability makes an object usable as a dictionary key and a set
member, because these data structures use the hash value internally.
All of Python’s immutable built-in objects are hashable, while no
mutable containers (such as lists or dictionaries) are. Objects which
are instances of user-defined classes are hashable by default; they
all compare unequal (except with themselves), and their hash value is
their id().
从最后一部分可以看出,用户定义的类(重点是我的)默认是可哈希的
docs 中没有提及 set
的可变性要求:
class set([iterable]) class frozenset([iterable]) Return a new set or
frozenset object whose elements are taken from iterable. The elements
of a set must be hashable. To represent sets of sets, the inner sets
must be frozenset objects. If iterable is not specified, a new empty
set is returned.)
对于 dict
再次要求是密钥是可散列的:
A mapping object maps hashable values to arbitrary objects. Mappings
are mutable objects. There is currently only one standard mapping
type, the dictionary. (For other containers see the built in list,
set, and tuple classes, and the collections module.)
经过更多研究后,我找到了我认为 set 和 dict 只允许不可变对象分别作为条目和键的原因,这是不正确的。我认为在这里澄清这一点对我来说很重要,因为我确信 Python 的新人和我以前一样有同样的疑问。我之前将不变性和可散列性混淆的原因有两个。第一个原因是所有内置的不可变对象(tuple、frozenset ...)都允许作为集合条目或字典键,而所有内置的可变容器对象都不允许。第二个原因实际上是这个问题的来源。我正在阅读 Mastering Object-Oriented Python 这本书。在解释 __hash__
函数的部分中,它引导 reader 认为不变性是散列性的先决条件。
不变性的定义是对于一个对象,在创建之后,你不能改变它已有的属性的值,也不能创建新的属性。所以它与散列性无关,散列性要求对象定义 __hash__()
和 __eq__()
方法,并且它们的散列值在其生命周期内是不可变的。实际上,可哈希对象的哈希值是不可变的。我想这就是这两个概念经常混淆的原因之一。
给定以下示例,
class A(object):
pass
a = A()
a.x = 1
很明显a是可变的,然后我把a放到了set里,
set([a])
成功了。为什么我可以将 "a" 之类的可变对象放入 set/dict 中? set/dict 难道不应该只允许不可变对象以便它们可以识别对象并避免重复吗?
Python 不测试 mutable 对象,它测试 hashable 对象。
自定义 class 实例默认可哈希。这很好,因为此类 classes 的默认 __eq__
实现仅测试实例 identity 并且散列基于相同的信息。
换句话说,改变实例属性的状态并不重要,因为实例的身份无论如何都是不可变的。
一旦您实施了考虑实例状态的 __hash__
和 __eq__
方法,您可能会遇到麻烦,应该停止改变该状态。只有这样,自定义 class 实例才不再适合存储在字典或集合中。
添加成员时 id
不会改变。所以没有理由不工作。
class A(object):
pass
a = A()
print(id(a))
a.x = 1
print(id(a))
根据 docs 的要求是它必须是可散列的并且可以比较:
An object is hashable if it has a hash value which never changes during its lifetime (it needs a hash() method), and can be compared to other objects (it needs an eq() or cmp() method). Hashable objects which compare equal must have the same hash value.
Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.
All of Python’s immutable built-in objects are hashable, while no mutable containers (such as lists or dictionaries) are. Objects which are instances of user-defined classes are hashable by default; they all compare unequal (except with themselves), and their hash value is their id().
从最后一部分可以看出,用户定义的类(重点是我的)默认是可哈希的
docs 中没有提及 set
的可变性要求:
class set([iterable]) class frozenset([iterable]) Return a new set or frozenset object whose elements are taken from iterable. The elements of a set must be hashable. To represent sets of sets, the inner sets must be frozenset objects. If iterable is not specified, a new empty set is returned.)
对于 dict
再次要求是密钥是可散列的:
A mapping object maps hashable values to arbitrary objects. Mappings are mutable objects. There is currently only one standard mapping type, the dictionary. (For other containers see the built in list, set, and tuple classes, and the collections module.)
经过更多研究后,我找到了我认为 set 和 dict 只允许不可变对象分别作为条目和键的原因,这是不正确的。我认为在这里澄清这一点对我来说很重要,因为我确信 Python 的新人和我以前一样有同样的疑问。我之前将不变性和可散列性混淆的原因有两个。第一个原因是所有内置的不可变对象(tuple、frozenset ...)都允许作为集合条目或字典键,而所有内置的可变容器对象都不允许。第二个原因实际上是这个问题的来源。我正在阅读 Mastering Object-Oriented Python 这本书。在解释 __hash__
函数的部分中,它引导 reader 认为不变性是散列性的先决条件。
不变性的定义是对于一个对象,在创建之后,你不能改变它已有的属性的值,也不能创建新的属性。所以它与散列性无关,散列性要求对象定义 __hash__()
和 __eq__()
方法,并且它们的散列值在其生命周期内是不可变的。实际上,可哈希对象的哈希值是不可变的。我想这就是这两个概念经常混淆的原因之一。