为什么元组可以包含可变项?

如果一个元组是不可变的,那么为什么它可以包含可变项?

当一个可变项(如列表)被修改时,它所属的元组仍然是不可变的,这似乎是一个矛盾。

39293 次浏览

这是个好问题。

关键是元组无法知道其中的对象是否是可变的。使对象可变的唯一方法是有一个改变其数据的方法。一般来说,没有办法检测到这一点。

另一个见解是,Python的容器实际上不包含任何东西。相反,它们保留对其他对象的引用。同样,Python的变量也不像编译语言中的变量;相反,变量名只是名称空间字典中的键,它们与对应的对象相关联。Ned Batchhelder在博客中很好地解释了这一点。无论哪种方式,对象只知道它们的引用计数;他们不知道这些引用是什么(变量、容器或Python内部)。

这两个洞见一起解释了您的困惑(为什么“包含”列表的不可变元组在底层列表更改时似乎也会更改)。事实上,元组并没有改变(它仍然具有与以前相同的对其他对象的引用)。元组不能改变(因为它没有突变方法)。当列表更改时,元组没有得到更改的通知(列表不知道它是由变量、元组还是另一个列表引用的)。

当我们讨论这个话题时,这里有一些其他的想法来帮助你完成什么是元组,它们是如何工作的,以及它们的预期用途的心智模型:

    元组的特征不在于其不可变性,而更多地在于其预期目的 元组是Python在一个屋檐下收集异构信息的方式。例如, s = ('www.python.org', 80) 将一个字符串和一个数字组合在一起,以便主机/端口对可以作为套接字(复合对象)传递。从这个角度来看,拥有可变组件是完全合理的
  1. 不可变性与另一个属性相辅相成,hashability。但是可哈希性并不是一个绝对的属性。如果元组的一个组件不是可哈希的,那么整个元组也是不可哈希的。例如,t = ('red', [10, 20, 30])是不可哈希的。

最后一个例子展示了一个包含字符串和列表的二元组。元组本身是不可变的(也就是说,它没有任何方法来改变它的内容)。同样,字符串是不可变的,因为字符串没有任何突变方法。list对象确实有突变方法,因此可以更改它。这表明可变性是对象类型的一个属性——有些对象具有可变方法,有些则没有。这不会因为对象嵌套而改变。

记住两件事。首先,不变性并不是魔法——它只是缺少变异方法。其次,对象不知道哪些变量或容器引用了它们——它们只知道引用计数。

希望,这对你有用:-)

我在这里大胆地说,这里的相关部分是,虽然你可以改变一个列表的内容,或一个对象的状态,包含在一个元组中,你不能改变的是对象或列表在那里。如果你有一些东西依赖于[3]是一个列表,即使是空的,那么我可以看到这是有用的。

元组是不可变的,因为元组本身不能扩展或收缩,而不是所有包含自身的项都是不可变的。否则元组是无趣的。

这是因为元组包含列表、字符串或数字。它们包含对其他对象的引用。不能改变元组包含的引用序列并不意味着你不能改变与这些引用相关的对象

< p > 1. 对象、值和类型(见:倒数第二段 < br > 2. 标准类型层次结构(参见:“不可变序列”)子> < /

你不能改变其项的id。所以它总是包含相同的项。

$ python
>>> t = (1, [2, 3])
>>> id(t[1])
12371368
>>> t[1].append(4)
>>> id(t[1])
12371368

首先,“不可改变”这个词;对不同的人来说有不同的含义。我特别喜欢Eric Lippert在他的博客文章 [档案2012-03-12]中对不可变性的分类。在书中,他列举了以下几种不变性:

  • Realio-trulio不变性
  • 写一次不变性
  • 冰棒不变性
  • 浅不变与深不变
  • 不变的外观
  • 观察不变性

这些元素可以以不同的方式组合在一起,从而创造出更多类型的不变性,而且我相信还有更多类型的不变性存在。您似乎对深层(也称为传递性)不可变感兴趣,在这种不可变对象中,不可变对象只能包含其他不可变对象。

这里的关键在于,深度不变性只是许许多多不变性中的一种。你可以选择你喜欢的任何一种,只要你意识到你的“不变”的概念。可能不同于别人对“不可改变”的概念。

根据我的理解,这个问题需要重新定义为一个关于设计决策的问题:为什么Python的设计者选择创建一个可以包含可变对象的不可变序列类型?

要回答这个问题,我们必须考虑元组的作用:它们作为通用的序列。考虑到这一点,很明显,为什么元组是不可变的,但可以包含可变对象。即:

  1. 元组fast和内存效率:元组比列表创建快,因为它们是不可变的。不可变性意味着元组可以被创建为常量并被加载,使用常量折叠。这也意味着它们的创建速度更快,内存效率更高,因为不需要过度分配等等。它们比随机物品访问列表要慢一些,但在解包方面(至少在我的机器上)又快了一些。如果元组是可变的,那么它们对于这些目的就不会那么快。

  2. 元组是general-purpose:元组需要能够包含任何类型的对象。它们用于(快速)执行如下操作:变长参数列表(通过函数定义中的*操作符)。如果元组不能保存可变对象,那么它们对于这样的事情就毫无用处了。Python将不得不使用列表,这可能会降低速度,并且肯定会降低内存效率。

所以你看,为了实现它们的目的,元组必须是不可变的,但也必须能够包含可变对象。如果Python的设计者想要创建一个不可变对象,以保证它“包含”的所有对象也是不可变的,那么他们就必须创建第三种序列类型。这种收益不值得额外的复杂性。

一个原因是在Python中没有通用的方法将可变类型转换为不可变类型(请参阅被拒绝的PEP 351相关的讨论了解为什么它被拒绝)。因此,如果有这个限制,就不可能在元组中放入各种类型的对象,包括几乎任何用户创建的不可哈希对象。

字典和集合有这个限制的唯一原因是它们要求对象是可哈希的,因为它们在内部实现为哈希表。但请注意,具有讽刺意味的是,字典和集本身是不可变(或可哈希)。元组不使用对象的哈希,因此它的可变性无关紧要。