为什么我们需要Python中的元组(或任何不可变的数据types)?

我已经阅读了几个python教程(例如Dive Into Python)以及Python.org上的语言参考 – 我不明白为什么语言需要元组。

与列表或集合相比,元组没有任何方法,如果我必须将元组转换为集合或列表才能对其进行sorting,那么首先使用元组有什么意义?

不变性?

为什么有人关心一个variables是否存在于内存中与原来分配不同的地方? Python中不可变性的整个业务似乎过分强调。

在C / C ++中,如果我分配一个指针并指向一些有效的内存,我不关心地址的位置,只要它在我使用它之前不是空的。

每当我引用该variables时,我不需要知道指针是否仍然指向原始地址。 我只是检查null并使用它(或不)。

在Python中,当我分配一个string(或元组)分配给x,然后修改string,为什么我关心,如果它是原始对象? 只要variables指向我的数据,那就重要了。

>>> x='hello' >>> id(x) 1234567 >>> x='good bye' >>> id(x) 5432167 

x仍然引用我想要的数据,为什么有人需要关心它的id是相同还是不同?

  1. 不可变对象可以允许实质性的优化; 这大概是为什么string在Java中也是不可变的,它们是与Python几乎分开开发的,几乎所有东西在真正函数式语言中都是不变的。

  2. 特别是在Python中,只有不可变的可以是可散列的(因此,集合成员或字典中的键)。 再次,这提供了优化,但不仅仅是“实质性的”(devise存储完全可变对象的体面散列表是一场噩梦 – 要么一拿到散列就把所有东西都复制一次,要么检查对象的散列是否是恶梦已经改变,因为你最后一次参考它的丑陋的头)。

优化问题示例:

 $ python -mtimeit '["fee", "fie", "fo", "fum"]' 1000000 loops, best of 3: 0.432 usec per loop $ python -mtimeit '("fee", "fie", "fo", "fum")' 10000000 loops, best of 3: 0.0563 usec per loop 

上面的答案都没有指出元组和列表的真正问题,很多Python的新手似乎都没有完全理解。

元组和列表服务于不同的目的。 列出存储同质数据。 你可以也应该有这样的列表:

 ["Bob", "Joe", "John", "Sam"] 

正确使用列表的原因是因为这些都是同类数据,特别是人名。 但是拿一个这样的列表:

 ["Billy", "Bob", "Joe", 42] 

这份名单是一个人的全名和他们的年龄。 这不是一种types的数据。 存储该信息的正确方法是在元组中,或者在一个对象中。 可以说我们有几个:

 [("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)] 

元组和列表的不可变性和可变性不是主要区别。 列表是相同种类的项目列表:文件,名称,对象。 元组是一组不同types的对象。 他们有不同的用途,许多Python编程人员滥用列表的元组是什么意思。

请不要。


编辑:

我认为这个博客文章解释了为什么我认为这比我更好: http : //news.e-scribe.com/397

如果我必须将一个元组转换为一个集合或列表才能sorting,那么首先使用元组有什么意义?

在这个特定的情况下,可能没有一点。 这是一个非问题,因为这不是你考虑使用元组的情况之一。

正如你所指出的,元组是不可变的。 具有不可变types的原因适用于元组:

  • 复制效率:而不是复制一个不可变的对象,你可以别名(将一个variables绑定到一个引用)
  • 比较效率:当您使用通过引用进行复制时,可以通过比较位置来比较两个variables,而不是内容
  • 实习:您最多需要存储任何不可变值的副本
  • 不需要在并发代码中同步对不可变对象的访问
  • const正确性:一些值不应该被允许改变。 这(对我来说)是不可变types的主要原因。

请注意,一个特定的Python实现可能无法使用上述所有function。

字典密钥必须是不可变的,否则,改变密钥对象的属性会使基础数据结构的不variables失效。 元组可以潜在地用作关键字。 这是const正确性的结果。

另请参阅从Dive Into Python中的 “ 元组简介 ”。

有时我们喜欢用对象作为字典键

对于它的价值,元组最近(2.6+)增长了index()count()方法

对于相同的基本数据结构(数组),我总是发现有两个完全独立的types是一个尴尬的devise,但实际上并不是一个真正的问题。 (每种语言都有它的瑕疵,包括Python在内,但这并不重要)

为什么有人关心一个variables是否存在于内存中与原来分配不同的地方? Python中不可变性的整个业务似乎过分强调。

这些是不同的事情。 可变性与存储在内存中的地点无关; 这意味着它指向东西不能改变。

Python对象创build后不能更改位置,可变或不可变。 (更准确地说,id()的值不能改变 – 实际上是一样的)。可变对象的内部存储可以改变,但这是一个隐藏的实现细节。

 >>> x='hello' >>> id(x) 1234567 >>> x='good bye' >>> id(x) 5432167 

这不是修改(“变异”)variables; 它创build一个同名的新variables,并丢弃旧variables。 比较变异操作:

 >>> a = [1,2,3] >>> id(a) 3084599212L >>> a[1] = 5 >>> a [1, 5, 3] >>> id(a) 3084599212L 

正如其他人所指出的,这允许使用数组作为字典的关键字,以及其他需要不变性的数据结构。

请注意,词典的键不一定是完全不可变的。 只有它被用作关键的部分需要是不可改变的; 对于某些用途,这是一个重要的区别。 例如,您可以有一个表示用户的类,它通过唯一的用户名来比较相等和散列。 然后,你可以在类上挂上其他可变数据 – “用户已login”等等。由于这不会影响相等或散列,所以将其用作字典中的键是完全有效的。 这在Python中并不常见; 我只是指出,因为有几个人声称密钥需要是“不可变的”,这只是部分正确的。 不过,我已经用C ++地图和集合多次使用过。

正如gnibbler在评论中提供的那样,Guido有一个意见没有被完全接受/赞赏:“列表是用于同质数据的,元组是用于异构数据的”。 当然,许多反对者认为这是一个列表中的所有元素应该是相同的types。

我喜欢以不同的方式来看待它,与其他人在过去一样:

 blue= 0, 0, 255 alist= ["red", "green", blue] 

请注意,即使types(alist [1])!= type(alist [2]),我认为alist也是同类的。

如果我可以改变元素的顺序,并且我的代码中没有问题(除了假设,例如“它应该被sorting”),那么应该使用一个列表。 如果不是(就像上面的blue元组),那么我应该使用一个元组。

它们很重要,因为它们保证了调用者所传递的对象不会被突变。 如果你这样做:

 a = [1,1,1] doWork(a) 

来电者无法保证通话​​结束后的价值。 然而,

 a = (1,1,1) doWorK(a) 

现在你作为调用者或者作为这个代码的读者知道a是一样的。 你总是可以为这个场景制作一个列表的副本,并通过它,但现在你正在浪费周期,而不是使用语义更有意义的语言结构。

你可以在这里看到关于这个的一些讨论

你的问题(和后续评论)关注的是id()是否在赋值过程中发生变化。 着眼于不可变对象replace和可变对象修改之间差异的后续影响,而不是差异本身可能不是最好的方法。

在继续之前,请确保下面演示的行为是您对Python的期望。

 >>> a1 = [1] >>> a2 = a1 >>> print a2[0] 1 >>> a1[0] = 2 >>> print a2[0] 2 

在这种情况下,a2的内容被改变,即使只有a1有一个新的值被赋值。 对比如下:

 >>> a1 = [1] >>> a2 = a1 >>> print a2[0] 1 >>> a1 = [2] >>> print a2[0] 1 

在后一种情况下,我们replace了整个列表,而不是更新其内容。 对于不可变types(如元组),这是唯一允许的行为。

为什么这很重要? 假设你有一个字典:

 >>> t1 = (1,2) >>> d1 = { t1 : 'three' } >>> print d1 {(1,2): 'three'} >>> t1[0] = 0 ## results in a TypeError, as tuples cannot be modified >>> t1 = (2,3) ## creates a new tuple, does not modify the old one >>> print d1 ## as seen here, the dict is still intact {(1,2): 'three'} 

使用一个元组,字典是安全的,不必将它的键从“下面”改变为散列到不同值的项。 这对于高效实施至关重要。