关于python:为什么元组包含可变项?

Why can tuples contain mutable items?

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

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


这是个很好的问题。

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

另一个洞见是,python的容器实际上不包含任何内容。相反,它们保留对其他对象的引用。同样,python的变量与编译语言中的变量不同;相反,变量名只是名称空间字典中的键,它们与相应的对象相关联。内德·巴切尔德在他的博客中很好地解释了这一点。不管怎样,对象只知道它们的引用计数;它们不知道这些引用是什么(变量、容器或Python内部)。

总之,这两个洞察解释了你的奥秘(当底层列表改变时,为什么不可变的元组"包含"列表似乎会改变)。事实上,元组没有改变(它对其他对象的引用仍然和以前一样)。元组无法更改(因为它没有突变方法)。当列表更改时,元组不会收到更改通知(列表不知道它是否被变量、元组或其他列表引用)。

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

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

  • 不变性与另一个属性hashability同时存在。但是哈希性不是绝对属性。如果元组的一个组件不可哈希,那么整个元组也不可哈希。例如,t = ('red', [10, 20, 30])不可散列。

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

    记住两件事。首先,不变不是魔法——它仅仅是缺少变异方法。第二,对象不知道什么变量或容器引用它们——它们只知道引用计数。

    希望,这对你有帮助:—)


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

    >1。对象、值和类型(请参见:从第二段到最后一段)>2。标准类型层次结构(请参见:"不可变序列")


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

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

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

  • 元组是通用的:元组需要能够包含任何类型的对象。它们用于(快速)执行诸如变长参数列表之类的操作(通过函数定义中的*操作符)。如果元组不能容纳可变的对象,那么它们对于这样的事情将是无用的。python必须使用列表,这可能会降低速度,而且肯定会降低内存效率。

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


    首先,"不变"这个词对不同的人来说意味着许多不同的事情。我特别喜欢埃里克·利珀特在他的博客文章中对不变性的分类。在这里,他列出了这些类型的不变性:

    • 实实在在不变性
    • 一次写入不可变
    • 冰棒不变性
    • 浅不变性与深不变性
    • 不变的立面
    • 观测不变性

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

    这一点的关键在于,深层不变性仅仅是众多不变性中的一种。你可以采取任何你喜欢的方式,只要你知道你的"不变"的概念可能不同于别人的"不变"的概念。


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

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

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


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

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


    从元组本身不能扩展或收缩的意义上讲,元组是不可变的,不是包含它们自己的所有项都是不可变的。否则,元组是无趣的。