为什么元组包含可变项?

qaz*_*wsx 173 python tuples list immutability

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

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

Ray*_*ger 195

这是一个很好的问题.

关键的见解是元组无法知道它们内部的对象是否可变.使对象变为可变的唯一方法是使用一种改变其数据的方法.通常,没有办法检测到这一点.

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

总之,这两个见解解释了你的神秘感(为什么当基础列表发生变化时,包含"列表"的不可变元组似乎会发生变化).实际上,元组没有改变(它仍然具有与之前所做的其他对象相同的引用).元组无法改变(因为它没有变异方法).当列表发生更改时,元组未收到有关更改的通知(列表不知道它是由变量,元组还是其他列表引用).

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

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

  2. 不变性与另一种属性,可持续性密切相关.但可持续性并非绝对属性.如果其中一个元组的组件不可清除,则整个元组也不可清除.例如,t = ('red', [10, 20, 30])不可清洗.

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

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

希望,这对你有用:-)

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

Ign*_*ams 170

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

1. 对象,值和类型(参见:倒数第二段)
2 .标准类型层次结构(参见:"不可变序列")

  • 这个问题含糊不清.这个答案完全解释了为什么元组包含可变对象的_possible_.它没有解释为什么元组[_designed_](http://stackoverflow.com/a/9758886/577088)包含可变对象.我认为后者是更相关的问题. (8认同)

Ken*_*nde 16

首先,"不可变"这个词对不同的人来说意味着许多不同的东西.我特别喜欢Eric Lippert在博客文章中对不变性进行分类的方式.在那里,他列出了这些不变性:

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

这些可以通过各种方式结合起来,以实现更多种类的不变性,而且我相信更多的存在.你似乎对这种不可变性的深度(也称为传递)不变性感兴趣,其中不可变对象只能包含其他不可变对象.

关键在于,深层不变性只是众多不可变性中的一种.你可以采用你喜欢的任何一种,只要你知道你的"不可变"的概念可能与别人的"不可变"概念不同.

  • Python元组具有浅(即非传递)不变性. (3认同)

sen*_*rle 16

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

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

  1. 元组快速且内存效率高:元组创建速度比列表快,因为它们是不可变的.不变性意味着可以使用常量折叠将元组创建为常量并按原样加载.这也意味着它们的创建速度更快,内存效率更高,因为不需要进行过度分配等等.它们比随机项目访问的列表一点,但对于解包(至少在我的机器上)来说再快一些.如果元组是可变的,那么它们就不会像这些目的一样快.

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

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


kev*_*kev 12

您无法更改id其项目.所以它总是包含相同的项目.

$ python
>>> t = (1, [2, 3])
>>> id(t[1])
12371368
>>> t[1].append(4)
>>> id(t[1])
12371368
Run Code Online (Sandbox Code Playgroud)


小智 5

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