如果一个元组是不可变的,那么为什么它可以包含可变项呢?
这似乎是一个矛盾,当可变项(如列表)确实被修改时,它所属的元组保持不变。
Answers:
这是一个很好的问题。
关键的见解是,元组无法知道其中的对象是否可变。使对象可变的唯一方法是拥有更改其数据的方法。通常,无法检测到此情况。
另一个见解是Python的容器实际上不包含任何东西。相反,它们保留对其他对象的引用。同样,Python的变量与编译语言中的变量不同。相反,变量名只是名称空间字典中的键,它们与对应的对象相关联。Ned Batchhelder在他的博客文章中很好地解释了这一点。无论哪种方式,对象仅知道其引用计数。他们不知道这些引用是什么(变量,容器或Python内部函数)。
这两种见解共同解释了您的奥秘(为什么当基础列表更改时,“包含”列表的不可变元组似乎也会更改)。实际上,元组没有改变(它对其他对象的引用与以前相同)。元组无法更改(因为它没有变异方法)。当列表更改时,没有通知元组更改(该列表不知道它是由变量,元组还是其他列表引用)。
当我们讨论该主题时,还有一些其他想法可以帮助您完善关于什么是元组,它们如何工作以及其预期用途的思维模型:
元组的特征较少在于其不变性,而其特征在于其预期目的。
元组是Python在一个屋檐下收集异构信息的一种方式。例如,
s = ('www.python.org', 80)
将字符串和数字组合在一起,以便主机/端口对可以作为套接字(复合对象)传递。从这个角度来看,具有可变的组件是完全合理的。
不变性与另一个属性(哈希性)密切相关。但是哈希性不是绝对的属性。如果元组的组成部分之一不可散列,则整个元组也不可散列。例如,t = ('red', [10, 20, 30])
不可散列。
最后一个示例显示了一个包含字符串和列表的2元组。元组本身是不可变的(即,它没有任何更改其内容的方法)。同样,字符串是不可变的,因为字符串没有任何突变方法。列表对象确实具有变异方法,因此可以对其进行更改。这表明可变性是对象类型的属性-有些对象具有突变方法,有些则没有。这并不会因为对象被嵌套而改变。
记住两件事。首先,不变性不是魔术,而是缺少突变方法。其次,对象不知道哪些变量或容器引用了它们-它们仅知道引用计数。
希望这对您有用:-)
hash()
因为从object()继承的所有内容都是可哈希的,因此子类需要显式关闭哈希。2)哈希不能保证不变性-易于创建可变的可哈希对象示例。3)与Python中的大多数容器一样,元组仅具有对基础对象的引用-它们没有义务检查它们并对其进行推断。
首先,“不变”一词对不同的人可能意味着许多不同的事物。我特别喜欢Eric Lippert在他的博客文章中对不变性的分类。在那里,他列出了这些不变性:
可以通过多种方式将它们组合起来,以实现更多种不变性,而且我敢肯定,还有更多种不变性。您似乎对深层(也称为传递)不变性感兴趣的一种不可变性,其中不可变对象只能包含其他不可变对象。
关键在于,深度不变性只是许多不变性中的一种。只要知道您的“不可变”概念可能与其他人的“不可变”概念不同,就可以采用您喜欢的任何一种。
据我所知,这个问题需要改写为关于设计决策的问题:Python的设计者为什么选择创建一个可以包含可变对象的不可变序列类型?
要回答这个问题,我们必须考虑的宗旨元组服务:他们作为快速,通用序列。考虑到这一点,很明显为什么元组是不可变的却可以包含可变对象。以机智:
元组速度快且内存效率高:元组的创建是比列表更快的,因为它们是不可变的。不变性意味着可以使用常量折叠将元组创建为常量并按此方式加载。这也意味着由于不需要过度分配等原因,它们的创建速度更快,内存使用效率更高。它们比随机访问列表的速度慢一点,但是对于拆包又要更快(至少在我的机器上)。如果元组是可变的,那么它们就不会达到这样的目的。
元组是通用的:元组需要能够包含任何类型的对象。它们习惯于(快速地)执行可变长度参数列表之类的事情(通过*
函数定义中的运算符)。如果元组不能容纳可变对象,那么它们对于这样的事情将毫无用处。Python必须使用列表,这可能会减慢速度,并且肯定会降低内存效率。
因此,您看到,为了实现其目的,元组必须是不可变的,而且还必须能够包含可变对象。如果Python的设计人员想要创建一个不可变的对象,以保证它“包含”的所有对象也是不可变的,那么他们将必须创建第三个序列类型。增益不值得额外的复杂性。
我将在这里走出去的肢体和说,这里的相关部分是,虽然你可以改变一个列表的内容或对象的状态,包含一个元组中,你不能改变该对象或列表在那里。如果您有一个依赖于事物[3]的列表,即使它为空,那么我会发现这很有用。