如果元组是不可变的,那为什么它可以包含可变项?
看似矛盾的是,当一个可变项如列表被修改时,它所属的元组维持不可变。
答案 0 :(得分:188)
这是一个很好的问题。
关键的洞察力是元组无法知道它们内部的对象是否可变。使对象变为可变的唯一方法是使用一种改变其数据的方法。一般来说,没有办法检测到这一点。
另一个见解是Python的容器实际上并不包含任何东西。相反,他们保留对其他对象的引用。同样,Python的变量与编译语言中的变量不同;相反,变量名只是命名空间字典中的键,它们与相应的对象相关联。 Ned Batchhelder在他的blog post中很好地解释了这一点。无论哪种方式,对象只知道他们的引用计数;他们不知道那些引用是什么(变量,容器或Python内部)。
这两个见解共同解释了你的谜团(为什么包含“列表”的不可变元组似乎在基础列表发生变化时会发生变化)。实际上,元组没有改变(它仍然具有与之前所做的其他对象相同的引用)。元组无法改变(因为它没有变异方法)。当列表发生更改时,元组未收到有关更改的通知(列表不知道它是由变量,元组还是其他列表引用)。
虽然我们正在谈论这个主题,但还有一些其他想法可以帮助您完成关于元组是什么,它们如何工作以及它们的预期用途的心理模型:
元组的特征在于它们的不变性,更多的是它们的预期目的
元组是Python在一个屋檐下收集异构信息的方式。例如,
s = ('www.python.org', 80)
汇集了一个字符串和一个数字,以便主机/端口对可以作为套接字(复合对象)传递。从这个角度来看,拥有可变组件是完全合理的。
不变性与另一个属性hashability齐头并进。但可持续性并非绝对属性。如果其中一个元组的组件不可清除,则整个元组也不可清除。例如,t = ('red', [10, 20, 30])
不可用。
最后一个示例显示了一个包含字符串和列表的2元组。元组本身不可变(即它没有任何改变其内容的方法)。同样,字符串是不可变的,因为字符串没有任何变异方法。列表对象确实有变异方法,因此可以更改。这表明可变性是对象类型的属性 - 一些对象具有变异方法而一些不具有变异方法。这不会因为对象嵌套而改变。
记住两件事。首先,不变性不是魔术 - 它只是缺少变异方法。其次,对象不知道哪些变量或容器引用它们 - 它们只知道引用计数。
希望,这对你有用: - )
答案 1 :(得分:167)
那是因为元组不包含列表,字符串或数字。它们包含对其他对象的引用。 1 无法更改元组包含的引用序列并不意味着您不能改变关联的对象与那些参考文献。 2
<子> 1。 Objects, values and types (see: second to last paragraph) 子>
<子> 2。 The standard type hierarchy (see: "Immutable sequences")
答案 2 :(得分:16)
首先,“不可变”这个词对不同的人来说意味着许多不同的东西。我特别喜欢Eric Lippert如何在his blog post中对不变性进行分类。在那里,他列出了这些不变性:
这些可以通过各种方式结合起来,以实现更多种类的不变性,而且我相信更多的存在。您似乎对深层(也称为传递)不变性感兴趣的不变性,其中不可变对象只能包含其他不可变对象。
关键在于,深层不变性只是众多不可变性中的一种。你可以采用你喜欢的任何一种,只要你知道你的“不可变”概念可能与别人的“不可变”概念不同。
答案 3 :(得分:16)
据我所知,这个问题需要重新描述为一个关于设计决策的问题:为什么Python的设计者选择创建一个可以包含可变对象的不可变序列类型?
要回答这个问题,我们必须考虑tuples服务的目的:它们用作快速,通用序列。考虑到这一点,很明显为什么元组是不可变的但可以包含可变对象。即:
元组快并且内存效率高:元组是faster to create than lists,因为它们是不可变的。不可变性意味着可以使用constant folding将元组创建为常量并按原样加载。这也意味着它们创建速度更快,内存效率更高,因为不需要进行过度分配等等。它们比随机项目访问列表有点slower,但是再次打包更快(至少在我的机器上) )。如果元组是可变的,那么对于诸如此类的目的它们就不会那么快。
元组是通用:元组需要能够包含任何类型的对象。他们习惯于(快速)做variable-length argument lists之类的事情(通过函数定义中的*
运算符)。如果元组无法容纳可变对象,那么对于这样的事情它们将毫无用处。 Python必须使用列表,这可能会减慢速度,并且肯定会降低内存效率。
所以你看,为了实现它们的目的,元组必须是不可变的,但也必须能够包含可变对象。如果Python的设计者想要创建一个不可变对象来保证它“包含”的所有对象也是不可变的,那么他们必须创建第三个序列类型。增益不值得额外的复杂性。
答案 4 :(得分:11)
您无法更改其项目的id
。所以它总是包含相同的项目。
$ python
>>> t = (1, [2, 3])
>>> id(t[1])
12371368
>>> t[1].append(4)
>>> id(t[1])
12371368
答案 5 :(得分:5)
我会在这里说说这里的相关部分是,虽然您可以更改列表的内容或元组中包含的对象的状态,但您无法更改的是那个对象或列表就在那里。如果你有一些东西依赖于东西[3]作为一个列表,即使是空的,那么我可以看到它是有用的。
答案 6 :(得分:2)
元组在元组本身不能扩展或缩小的意义上是不可变的,而不是所有包含它们的项都是不可变的。否则元组很无聊。
答案 7 :(得分:2)
一个原因是Python中没有通用的方法将可变类型转换为不可变类型(请参阅被拒绝的PEP 351,而linked discussion则说明它被拒绝的原因。因此,如果它具有此限制,则不可能将各种类型的对象放入元组中,包括几乎任何用户创建的不可清除对象。
字典和集合具有此限制的唯一原因是它们要求对象是可散列的,因为它们在内部实现为散列表。但请注意,具有讽刺意味的是,字典和集合本身不不可变(或可散列)。元组不使用对象的哈希值,因此它的可变性无关紧要。