如果一个元组是不可变的,那么为什么它可以包含可变项?
当一个可变项(如列表)被修改时,它所属的元组仍然是不可变的,这似乎是一个矛盾。
如果一个元组是不可变的,那么为什么它可以包含可变项?
当一个可变项(如列表)被修改时,它所属的元组仍然是不可变的,这似乎是一个矛盾。
这是个好问题。
关键是元组无法知道其中的对象是否是可变的。使对象可变的唯一方法是有一个改变其数据的方法。一般来说,没有办法检测到这一点。
Another insight is that Python's containers don't actually contain anything. Instead, they keep references to other objects. Likewise, Python's variables aren't like variables in compiled languages; instead the variable names are just keys in a namespace dictionary where they are associated with a corresponding object. Ned Batchhelder explains this nicely in his blog post. Either way, objects only know their reference count; they don't know what those references are (variables, containers, or the Python internals).
这两个洞见一起解释了您的困惑(为什么“包含”列表的不可变元组在底层列表更改时似乎也会更改)。事实上,元组并没有改变(它仍然具有与以前相同的对其他对象的引用)。元组不能改变(因为它没有突变方法)。当列表更改时,元组没有得到更改的通知(列表不知道它是由变量、元组还是另一个列表引用的)。
当我们讨论这个话题时,这里有一些其他的想法来帮助你完成什么是元组,它们是如何工作的,以及它们的预期用途的心智模型:
Tuples are characterized less by their immutability and more by their intended purpose. Tuples are Python's way of collecting heterogeneous pieces of information under one roof. For example, s = ('www.python.org', 80) brings together a string and a number so that the host/port pair can be passed around as a socket, a composite object. Viewed in that light, it is perfectly reasonable to have mutable components. Immutability goes hand-in-hand with another property, hashability. But hashability isn't an absolute property. If one of the tuple's components isn't hashable, then the overall tuple isn't hashable either. For example, t = ('red', [10, 20, 30]) isn't hashable.
最后一个例子展示了一个包含字符串和列表的二元组。元组本身是不可变的(也就是说,它没有任何方法来改变它的内容)。同样,字符串是不可变的,因为字符串没有任何突变方法。list对象确实有突变方法,因此可以更改它。这表明可变性是对象类型的一个属性——有些对象具有可变方法,有些则没有。这不会因为对象嵌套而改变。
记住两件事。首先,不变性并不是魔法——它只是缺少变异方法。其次,对象不知道哪些变量或容器引用了它们——它们只知道引用计数。
希望,这对你有用:-)
我在这里大胆地说一下,这里的相关部分是,虽然你可以改变一个列表的内容,或者一个对象的状态,包含在一个元组中,但你不能改变的是对象或列表在那里。如果你有一些东西依赖于[3]是一个列表,即使是空的,那么我可以看到这是有用的。
这是因为元组不包含列表、字符串或数字。它们包含对其他对象的引用不能改变元组包含的引用序列并不意味着你不能改变与这些引用相关的对象
1. 对象、值和类型(见:倒数第二段) 2. 标准类型层次结构(参见:“不可变序列”)
不能更改其项的id。所以它总是包含相同的项。
$ python
>>> t = (1, [2, 3])
>>> id(t[1])
12371368
>>> t[1].append(4)
>>> id(t[1])
12371368
首先,“不可变”这个词对不同的人有不同的含义。我特别喜欢Eric Lippert在他的博客文章中对不变性的分类。在书中,他列举了以下几种不变性:
Realio-trulio不变性 写一次不变性 冰棒不变性 浅不变与深不变 不变的外观 观察不变性
这些元素可以以不同的方式组合在一起,从而创造出更多类型的不变性,而且我相信还有更多类型的不变性存在。您似乎对深层(也称为传递性)不可变感兴趣,在这种不可变对象中,不可变对象只能包含其他不可变对象。
这里的关键在于,深度不变性只是许许多多不变性中的一种。你可以采用你喜欢的任何一种,只要你意识到你的“不可变”概念可能与别人的“不可变”概念不同。
根据我的理解,这个问题需要重新定义为一个关于设计决策的问题:为什么Python的设计者选择创建一个可以包含可变对象的不可变序列类型?
要回答这个问题,我们必须考虑元组的用途:它们作为快速、通用的序列。考虑到这一点,很明显,为什么元组是不可变的,但可以包含可变对象。即:
Tuples are fast and memory efficient: Tuples are faster to create than lists because they are immutable. Immutability means that tuples can be created as constants and loaded as such, using constant folding. It also means they're faster and more memory efficient to create because there's no need for overallocation, etc. They're a bit slower than lists for random item access, but faster again for unpacking (at least on my machine). If tuples were mutable, then they wouldn't be as fast for purposes such as these. Tuples are general-purpose: Tuples need to be able to contain any kind of object. They're used to (quickly) do things like variable-length argument lists (via the * operator in function definitions). If tuples couldn't hold mutable objects, they would be useless for things like this. Python would have to use lists, which would probably slow things down, and would certainly be less memory efficient.
所以你看,为了实现它们的目的,元组必须是不可变的,但也必须能够包含可变的对象。如果Python的设计者想要创建一个不可变对象,以保证它“包含”的所有对象也是不可变的,那么他们就必须创建第三种序列类型。这种收益不值得额外的复杂性。
一个原因是,在Python中没有将可变类型转换为不可变类型的通用方法(请参阅被拒绝的PEP 351,以及关于它为什么被拒绝的链接讨论)。因此,如果有这个限制,就不可能在元组中放入各种类型的对象,包括几乎任何用户创建的不可哈希对象。
字典和集合有这个限制的唯一原因是它们要求对象是可哈希的,因为它们在内部实现为哈希表。但是要注意,具有讽刺意味的是,字典和集合本身并不是不可变的(或可哈希的)。元组不使用对象的哈希,因此它的可变性无关紧要。