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

问题:元组为什么可以包含可变项?

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

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

If a tuple is immutable then why can it contain mutable items?

It is seemingly a contradiction that when a mutable item such as a list does get modified, the tuple it belongs to maintains being immutable.


回答 0

这是一个很好的问题。

关键的见解是,元组无法知道其中的对象是否可变。使对象可变的唯一方法是拥有更改其数据的方法。通常,无法检测到此情况。

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

这两种见解共同解释了您的奥秘(为什么当基础列表更改时,“包含”列表的不可变元组似乎也会更改)。实际上,元组没有改变(它对其他对象的引用与以前相同)。元组无法更改(因为它没有变异方法)。当列表更改时,没有通知元组更改(该列表不知道它是由变量,元组还是其他列表引用)。

当我们讨论该主题时,还有一些其他想法可以帮助您完善关于什么是元组,它们如何工作以及其预期用途的思维模型:

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

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

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

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

希望这对您有用:-)

That’s an excellent question.

The key insight is that tuples have no way of knowing whether the objects inside them are mutable. The only thing that makes an object mutable is to have a method that alters its data. In general, there is no way to detect this.

Another insight is that Python’s containers don’t actually contain anything. Instead, they keep references to other objects. Likewise, Python’s variables aren’t like variables in compiled languages; instead the variable names are just keys in a namespace dictionary where they are associated with a corresponding object. Ned Batchhelder explains this nicely in his blog post. Either way, objects only know their reference count; they don’t know what those references are (variables, containers, or the Python internals).

Together, these two insights explain your mystery (why an immutable tuple “containing” a list seems to change when the underlying list changes). In fact, the tuple did not change (it still has the same references to other objects that it did before). The tuple could not change (because it did not have mutating methods). When the list changed, the tuple didn’t get notified of the change (the list doesn’t know whether it is referred to by a variable, a tuple, or another list).

While we’re on the topic, here are a few other thoughts to help complete your mental model of what tuples are, how they work, and their intended use:

  1. Tuples are characterized less by their immutability and more by their intended purpose.
    Tuples are Python’s way of collecting heterogeneous pieces of information under one roof. For example, s = ('www.python.org', 80) brings together a string and a number so that the host/port pair can be passed around as a socket, a composite object. Viewed in that light, it is perfectly reasonable to have mutable components.

  2. Immutability goes hand-in-hand with another property, hashability. But hashability isn’t an absolute property. If one of the tuple’s components isn’t hashable, then the overall tuple isn’t hashable either. For example, t = ('red', [10, 20, 30]) isn’t hashable.

The last example shows a 2-tuple that contains a string and a list. The tuple itself isn’t mutable (i.e. it doesn’t have any methods that for changing its contents). Likewise, the string is immutable because strings don’t have any mutating methods. The list object does have mutating methods, so it can be changed. This shows that mutability is a property of an object type — some objects have mutating methods and some don’t. This doesn’t change just because the objects are nested.

Remember two things. First, immutability is not magic — it is merely the absence of mutating methods. Second, objects don’t know what variables or containers refer to them — they only know the reference count.

Hope, this was useful to you :-)


回答 1

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

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

That’s because tuples don’t contain lists, strings or numbers. They contain references to other objects.1 The inability to change the sequence of references a tuple contains doesn’t mean that you can’t mutate the objects associated with those references.2

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


回答 2

首先,“不变”一词对不同的人可能意味着许多不同的事物。我特别喜欢Eric Lippert在他的博客文章中对不变性的分类。在那里,他列出了这些不变性:

  • 真实性不变
  • 一次写入不变性
  • 冰棒不可变性
  • 浅与深不变性
  • 不变的外墙
  • 观察不变性

可以通过多种方式将它们组合起来,以实现更多种不变性,而且我敢肯定,还有更多种不变性。您似乎对深层(也称为传递)不变性感兴趣的一种不可变性,其中不可变对象只能包含其他不可变对象。

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

First of all, the word “immutable” can mean many different things to different people. I particularly like how Eric Lippert categorized immutability in his blog post. There, he lists these kinds of immutability:

  • Realio-trulio immutability
  • Write-once immutability
  • Popsicle immutability
  • Shallow vs deep immutability
  • Immutable facades
  • Observational immutability

These can be combined in various ways to make even more kinds of immutability, and I’m sure more exist. The kind of immutability you seems interested in deep (also known as transitive) immutability, in which immutable objects can only contain other immutable objects.

The key point of this is that deep immutability is only one of many, many kinds of immutability. You can adopt whichever kind you prefer, as long as you are aware that your notion of “immutable” probably differs from someone else’s notion of “immutable”.


回答 3

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

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

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

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

因此,您看到,为了实现其目的,元组必须是不可变的,而且还必须能够包含可变对象。如果Python的设计人员想要创建一个不可变的对象,以保证它“包含”的所有对象也是不可变的,那么他们将必须创建第三个序列类型。增益不值得额外的复杂性。

As I understand it, this question needs to be rephrased as a question about design decisions: Why did the designers of Python choose to create an immutable sequence type that can contain mutable objects?

To answer this question, we have to think about the purpose tuples serve: they serve as fast, general-purpose sequences. With that in mind, it becomes quite obvious why tuples are immutable but can contain mutable objects. To wit:

  1. Tuples are fast and memory efficient: Tuples are faster to create than lists because they are immutable. Immutability means that tuples can be created as constants and loaded as such, using constant folding. It also means they’re faster and more memory efficient to create because there’s no need for overallocation, etc. They’re a bit slower than lists for random item access, but faster again for unpacking (at least on my machine). If tuples were mutable, then they wouldn’t be as fast for purposes such as these.

  2. Tuples are general-purpose: Tuples need to be able to contain any kind of object. They’re used to (quickly) do things like variable-length argument lists (via the * operator in function definitions). If tuples couldn’t hold mutable objects, they would be useless for things like this. Python would have to use lists, which would probably slow things down, and would certainly be less memory efficient.

So you see, in order to fulfill their purpose, tuples must be immutable, but also must be able to contain mutable objects. If the designers of Python wanted to create an immutable object that guarantees that all the objects it “contains” are also immutable, they would have to create a third sequence type. The gain is not worth the extra complexity.


回答 4

您不能更改id其项目。因此它将始终包含相同的项目。

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

You cannot change the id of its items. So it will always contain the same items.

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

回答 5

我将在这里走出去的肢体和说,这里的相关部分是,虽然你可以改变一个列表的内容或对象的状态,包含一个元组中,你不能改变对象或列表在那里。如果您有一个依赖于事物[3]的列表,即使它为空,那么我会发现这很有用。

I’ll go out on a limb here and say that the relevant part here is that while you can change the contents of a list, or the state of an object, contained within a tuple, what you can’t change is that the object or list is there. If you had something that depended on thing[3] being a list, even if empty, then I could see this being useful.


回答 6

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

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

One reason is that there is no general way in Python to convert a mutable type into an immutable one (see the rejected PEP 351, and the linked discussion for why it was rejected). Thus, it would be impossible to put various types of objects in tuples if it had this restriction, including just about any user-created non-hashable object.

The only reason that dictionaries and sets have this restriction is that they require the objects to be hashable, since they are internally implemented as hash tables. But note that, ironically, dictionaries and sets themselves are not immutable (or hashable). Tuples do not use an object’s hash, so its mutability does not matter.


回答 7

从元组本身不能扩展或收缩的意义上讲,元组是不可变的,并不是其中包含的所有项都是不可变的。否则,元组变钝。

A tuple is immutable in the sense that the tuple itself can not expand or shrink, not that all the items contained themselves are immutable. Otherwise tuples are dull.