为啥元组可以包含可变项?

Posted

技术标签:

【中文标题】为啥元组可以包含可变项?【英文标题】:Why can tuples contain mutable items?为什么元组可以包含可变项? 【发布时间】:2012-04-03 02:25:57 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

这是一个很好的问题。

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

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

这两个见解共同解释了您的谜团(为什么“包含”列表的不可变元组似乎会随着基础列表的变化而变化)。事实上,元组并没有改变(它对其他对象的引用仍然与以前相同)。元组不能改变(因为它没有变异方法)。当列表发生变化时,元组没有收到更改通知(列表不知道它是由变量、元组还是其他列表引用)。

虽然我们正在讨论这个主题,但这里有一些其他想法可以帮助您完成关于元组是什么、它们如何工作以及它们的预期用途的心理模型:

    元组的特征较少在于其不变性,而更多在于其预期目的。 元组是 Python 在一个屋檐下收集异构信息的一种方式。例如, s = ('www.python.org', 80) 将字符串和数字组合在一起,以便主机/端口对可以作为套接字(复合对象)传递。从这个角度来看,拥有可变组件是完全合理的。

    不变性与另一个属性 hashability 密切相关。但是哈希性并不是一个绝对的属性。如果元组的组件之一不可散列,则整个元组也不可散列。例如,t = ('red', [10, 20, 30]) 不可散列。

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

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

希望,这对你有用:-)

【讨论】:

“元组无法知道其中的对象是否可变”这不是错的。我们可以检测引用是否实现了哈希方法,那么它是不可变的。就像 dic 或 set 一样。这难道不是更多元组的设计决策吗? @garg10may 1) 不调用hash() 就不容易检测散列性,因为从 object() 继承的所有内容都是可散列的,因此子类需要显式关闭散列。 2) 哈希性并不能保证不变性——很容易制作可变的可哈希对象的示例。 3) 元组,就像 Python 中的大多数容器一样,只是对底层对象的引用——它们没有义务检查它们并对其进行推断。【参考方案2】:

这是因为元组包含列表、字符串或数字。它们包含对其他对象的引用1 无法更改元组包含的引用序列并不意味着您不能改变与这些对象关联的对象参考文献。2

1。 Objects, values and types (see: second to last paragraph)2. The standard type hierarchy (see: "Immutable sequences")

【讨论】:

这个问题有歧义。这个答案充分解释了为什么元组可能包含可变对象。它没有解释为什么元组是designed 来包含可变对象。我认为后者是更中肯的问题。【参考方案3】:

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

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

    元组快速且内存高效:元组是faster to create than lists,因为它们是不可变的。不变性意味着可以使用constant folding 将元组创建为常量并按原样加载。这也意味着它们的创建速度更快,内存效率更高,因为不需要过度分配等。它们比随机项目访问列表有点slower,但解包速度更快(至少在我的机器上)。如果元组是可变的,那么它们对于这些目的就不会那么快了。

    元组是通用的:元组需要能够包含任何类型的对象。他们习惯于(快速)执行variable-length argument lists 之类的操作(通过函数定义中的* 运算符)。如果元组不能保存可变对象,那么它们对于这样的事情将毫无用处。 Python 将不得不使用列表,这可能会减慢速度,并且肯定会降低内存效率。

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

【讨论】:

【参考方案4】:

首先,“不可变”这个词对不同的人可能意味着许多不同的东西。我特别喜欢 Eric Lippert 在his blog post [archive 2012-03-12] 中对不变性进行分类的方式。在那里,他列出了这些类型的不变性:

Realio-trulio 不变性 一次写入不变性 冰棒不变性 浅层不变性与深层不变性 不可变的外墙 观察不变性

这些可以以各种方式组合,以产生更多种类的不变性,我相信还有更多。您似乎对深层(也称为传递)不变性感兴趣的那种不变性,其中不可变对象只能包含其他不可变对象。

其中的关键点在于,深度不变性只是众多不变性中的一种。您可以采用您喜欢的任何一种,只要您知道您的“不可变”概念可能与其他人的“不可变”概念不同。

【讨论】:

Python 元组具有什么样的不变性? Python 元组具有浅(又名非传递)不变性。【参考方案5】:

您不能更改其项目的id。所以它总是包含相同的项目。

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

【讨论】:

这是对上述例子最恰当的演示。元组引用了那些不会改变的对象,尽管最多有一个可变组件会使整个元组无法散列。【参考方案6】:

我会在这里冒昧地说一下,这里的相关部分是,虽然您可以更改包含在元组中的列表的内容或对象的状态,但您无法更改的是那个对象或列表在那里。如果你有一些依赖于 thing[3] 作为列表的东西,即使是空的,那么我可以看到这很有用。

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

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

【讨论】:

以上是关于为啥元组可以包含可变项?的主要内容,如果未能解决你的问题,请参考以下文章

PySpark UDF 返回可变大小的元组

元组&字典

数据类型-元组

Python基础--不可变序列:元组

python中为啥说元组不可改变

第二周小结