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

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

看起来是一个矛盾,当一个可变项如列表被修改时,它所属的元组保持不变。

这是一个很好的问题。

关键的见解是,元组无法知道它们内部的对象是否是可变的。 唯一让对象变化的是有一个方法来改变它的数据。 一般来说,没有办法检测到这一点。

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

总之,这两个见解解释了你的神秘(为什么一个不可变的元组“包含”一个列表似乎改变时底层列表的变化)。 事实上,这个元组没有改变(它仍然和之前的其他对象有相同的引用)。 元组不能改变(因为它没有变异方法)。 当列表改变时,元组没有得到改变的通知(列表不知道它是否被variables,元组或其他列表引用)。

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

  1. 元组的特点是不变的,而更多的是它们的预期目的。
    元组是Python在同一屋檐下收集异类信息的方式。 例如, s = ('www.python.org', 80)将一个string和一个数字汇集在一起​​,以便主机/端口对可以作为套接字(一个复合对象)传递。 从这个angular度来看,有可变组件是完全合理的。

  2. 不变性与另一个属性可操作性相结合 。 但可用性不是绝对的财产。 如果元组的其中一个元素不可散列,那么整个元组也不可散列。 例如, t = ('red', [10, 20, 30])是不可散列的。

最后一个例子显示了一个包含一个string和一个列表的2元组。 元组本身是不可变的(即它没有任何改变其内容的方法)。 同样,string是不可变的,因为string没有任何变异的方法。 列表对象确实有变异的方法,所以可以改变。 这表明可变性是对象types的属性 – 一些对象具有变异的方法,有些则不具有变异性。 这不会因为对象被嵌套而改变。

记住两件事。 首先,不可变性不是魔术 – 仅仅是没有变异的方法。 其次,对象不知道什么variables或容器引用他们 – 他们只知道引用计数。

希望这对你有用:-)

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

1. 对象,值和types(参见:倒数第二段)
2. 标准types层次结构(请参阅:“不可变序列”)

首先,“不变”这个词对不同的人来说可能意味着很多不同的东西。 我特别喜欢Eric Lippert在他的博客文章中将不变性分类。 在那里,他列出了这种不变性:

  • 实际的不变性
  • 一次写入不变性
  • 冰棒不变性
  • 浅与深不变性
  • 不变的外立面
  • 观察不变性

这些可以以各种方式结合,使更多的不变性,我相信更多的存在。 你似乎对深层(也称为传递)不变性感兴趣的不变性,其中不可变对象只能包含其他不可变对象。

关键在于深不可变性只是众多不变性之一。 只要你意识到你的“不可变的”概念可能与别人的“不可变的”概念有所不同,你可以采取任何你喜欢的方式。

据我所知,这个问题需要作为一个关于devise决定的问题来重新考虑:为什么Python的devise者select创build一个不可变的序列types,可以包含可变对象?

为了回答这个问题,我们必须考虑元组的作用 :它们作为快速通用的序列。 考虑到这一点,为什么元组是不​​可变的,但可以包含可变对象就变得非常明显。 以机智:

  1. 元组快速且高效地存储:元组创build速度快于列表,因为它们是不可变的。 不变性意味着元组可以被创build为常量,并使用常量折叠加载。 这也意味着它们更快,更有效率地创build内存,因为不需要重新分配等等。它们比随机物品访问列表要慢一些,但是拆包(至less在我的机器上)又快一些。 如果元组是可变的,那么它们就不会像这样的目的那么快。

  2. 元组是通用的 :元组需要能够包含任何types的对象。 他们习惯于(快速)做可变长参数列表 (通过函数定义中的*运算符)。 如果元组无法容纳可变对象,那么对于这样的事情来说,它们是无用的。 Python将不得不使用列表,这可能会减慢速度,肯定会减less内存的效率。

所以你看,为了达到目的,元组必须是不可变的,而且必须能够包含可变对象。 如果Python的devise者想创build一个不可变的对象来保证它所“包含”的所有对象也是不可变的,那么他们将不得不创build第三个序列types。 收益不值得额外的复杂性。

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

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

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

元组是不可变的,因为元组本身不能扩展或缩小,并不是所有包含的项都是不可变的。 否则元组是无聊的。

其中一个原因是,在Python中没有通用的方法来将可变types转换为不可变types(参见拒绝的PEP 351 ,以及为什么被拒绝的链接讨论 )。 因此,如果有这种限制,就不可能把各种types的对象放在元组中,包括任何用户创build的不可哈希对象。

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