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

pyN*_*Guy 138 python tuples

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

与列表或集合相比,元组没有方法,如果我必须将元组转换为集合或列表以便能够对它们进行排序,那么首先使用元组有什么意义呢?

不变性?

为什么有人关心变量是否存在于内存中的不同位置而不是最初分配的位置?这整个Python的不变性业务似乎过分强调.

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

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

在Python中,当我分配一个字符串(或元组)将其分配给x时,然后修改字符串,为什么我关心它是否是原始对象?只要变量指向我的数据,那就重要了.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*lli 119

  1. 不可变对象可以允许实质性的优化; 这可能是为什么字符串在Java中也是不可变的,它是相当独立开发的,但与Python大致相同,而且几乎所有东西都是真正的函数式语言中不可变的.

  2. 特别是在Python中,只有不可变的可以是可散列的(因此,集合的成员或字典中的键).同样,这提供了优化,但远远不仅仅是"实质性"(设计存储完全可变对象的合适哈希表是一场噩梦 - 要么你在哈希它时立即复制所有东西,或者检查对象的哈希是否是噩梦自从你上次参考它以后它已经改变了它丑陋的脑袋).

优化问题示例:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • @musicfreak我认为你在滥用"过早优化是所有邪恶的根源".在应用程序中进行过早优化之间存在巨大差异(例如,说"元组比列表更快,所以我们将在所有应用程序中仅使用元组!")并进行基准测试.Alex的基准是富有洞察力的,并且知道构建元组比构建列表更快可能有助于我们在未来的优化操作中(当它真正需要时). (11认同)
  • @musicfreak,看看我刚刚做的编辑,构建一个元组的速度比构建同等列表快7.6倍 - 现在你不能说你"从来没有见过明显的差异",除非你的定义"明显"真是**真是奇特...... (9认同)
  • 对于任何想知道的人 - 我们能够通过从列表切换到元组来削减一个多小时的数据处理. (7认同)
  • @ACoolie,这完全由`随机'调用主导(尝试这样做,你会看到!),所以不是很重要.尝试`python -mtimeit -s"x = 23""[x,x]"`你会看到一个更有意义的加速2-3次,用于构建元组和构建列表. (6认同)
  • @Alex,"构建"一个元组真的比"构建列表"快,或者我们是否看到Python运行时缓存元组的结果?看来后者对我来说. (5认同)
  • @Alex:<插入有关过早优化的过度使用的引用>我已经看到几微秒的改进,但是这真的会改变整个应用程序的性能吗?我对此表示怀疑.我曾经重写了一个实时应用程序的一部分,该应用程序广泛使用元组来代替使用列表,并且由于它没有明显的性能影响,所以我支持我原来的陈述. (3认同)
  • 突然间,我有很多想要成为元组的"常量"序列 (3认同)
  • 我不喜欢优化参数,因为我从未在元组和列表之间看到过明显的性能差异.但是,我会给第二点+1. (2认同)
  • @Tryptych,如果你担心要缓存的元组,请比较`python -m timeit -s"x = 0""x + = 1;(x,x)"`和`python -m timeit -s"x = 0 ""x + = 1; [x,x]"`(减去`python -m timeit -s"x = 0""x + = 1"`删除该组件) (2认同)

Gra*_*aul 40

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

元组和列表用于不同的目的.列表存储同质数据.您可以而且应该有这样的列表:

["Bob", "Joe", "John", "Sam"]
Run Code Online (Sandbox Code Playgroud)

正确使用列表的原因是因为这些都是同类数据,特别是人名.但是采取这样的列表:

["Billy", "Bob", "Joe", 42]
Run Code Online (Sandbox Code Playgroud)

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

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]
Run Code Online (Sandbox Code Playgroud)

元组和列表的不变性和可变性不是主要区别.列表是相同类型的项目列表:文件,名称,对象.元组是不同类型对象的分组.它们有不同的用途,许多Python编码器滥用列表来表示元组的含义.

请不要.


编辑:

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

  • 我也非常不同意这个答案.数据的同质性​​与是否应该使用列表或元组完全无关.Python中没有任何内容表明这种区别. (13认同)
  • 几年前Guido也提到了这一点.http://aspn.activestate.com/ASPN/Mail/Message/python-list/1566320 (13认同)
  • 我认为你的愿景至少不是我同意的,不了解其他人. (12认同)
  • 尽管Guido(Python的设计者)打算将列表用于同构数据和异构的元组,但事实是该语言并未强制执行此操作.因此,我认为这种解释更像是一种风格问题.事实上,在许多人的典型用例中,列表往往是类似数组的,而元组往往是类似记录的.但这不应该阻止人们使用异构数据列表,如果它更适合他们的问题.正如Python的禅宗所说:实用性胜过纯洁. (11认同)
  • @Glenn,你基本上是错的.元组的主要用途之一是作为复合数据类型,用于存储相关的多个数据.您可以迭代元组并执行许多相同操作的事实不会改变这一点.(作为参考考虑,许多其他语言中的元组不具有与列表对应物相同的可迭代特性) (8认同)
  • 我之所以投票,原因只有一个."正确使用列表的原因是因为它们都是同质类型的数据"和"存储该信息的正确方法是在元组中"都是你没有理由的断言.据我所知,一个等效的答案可以反转元组和列表并传达相同数量的信息. (5认同)
  • 这是几乎任何其他语言元组的有效点,但它不适用于Python.语言本身并不限制列表仅用于同类数据,因此您可以以任何方式自由使用它们.如果它们是以这种特定方式使用的,为什么语言不能强制执行呢?无论哪种方式,你的答案都不是真的有效. (2认同)
  • 这个答案没有解释.想象一下有人告诉你在你的鞋子之前穿袜子,你问他们为什么.他们回答说:"这是正确的方式,加上莎士比亚,爱因斯坦和伦勃朗都这样做,你不比他们聪明吗?" 在基本逻辑层面,这种反应失败了:(1)提出问题,一个谬论,其中断言被用作相同断言正确的证据,以及(2)权威论证,使用专家的观点作为证据观点是正确的.专家持有观点的原因,而不是他持有观点的事实. (2认同)

out*_*tis 22

如果我必须将一个元组转换为一个集合或列表以便能够对它们进行排序,那么首先使用元组有什么意义呢?

在这种特殊情况下,可能没有意义.这不是问题,因为这不是您考虑使用元组的情况之一.

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

  • 复制效率:不是复制不可变对象,而是可以别名(将变量绑定到引用)
  • 比较效率:当您使用按引用复制时,您可以通过比较位置而不是内容来比较两个变量
  • 实习:您需要存储任何不可变值的最多一个副本
  • 没有必要在并发代码中同步对不可变对象的访问
  • const正确性:不应允许某些值发生变化.这(对我而言)是不可变类型的主要原因.

请注意,特定的Python实现可能无法使用上述所有功能.

字典键必须是不可变的,否则更改键对象的属性可能会使底层数据结构的不变量无效.因此,元组可以用作键.这是const正确性的结果.

另请参阅Dive Into Python中的 " 介绍元组 " .

  • id((1,2,3))== id((1,2,3))为false.您无法仅通过比较位置来比较元组,因为无法保证它们是通过引用复制的. (2认同)

Joh*_*ooy 15

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

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

  • +1:作为字典键的可变列表(或可变集或可变字典)不起作用.因此,我想我们需要不可变列表("元组"),冻结集,以及......好吧......冻结字典. (5认同)

Gle*_*ard 9

我总是发现对于相同的基本数据结构(数组)有两个完全独立的类型是一个笨拙的设计,但在实践中不是一个真正的问题.(每种语言都有疣,包括Python,但这不是一个重要的.)

为什么有人关心变量是否存在于内存中的不同位置而不是最初分配的位置?这整个Python的不变性业务似乎过分强调.

这些是不同的东西.可变性与存储在内存中的位置无关; 它意味着它指向东西不能改变.

Python对象在创建后不能更改位置,可变或不可更改.(更准确地说,id()的值不能改变 - 在实践中也是如此.)可变对象的内部存储可以改变,但这是一个隐藏的实现细节.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167
Run Code Online (Sandbox Code Playgroud)

这不是修改("变异")变量; 它正在创建一个具有相同名称的新变量,并丢弃旧变量.与变异操作相比:

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L
Run Code Online (Sandbox Code Playgroud)

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

请注意,词典的键不必完全不可变.只有用作密钥的部分需要是不可变的; 对于某些用途,这是一个重要的区别.例如,您可以拥有一个表示用户的类,该类通过唯一的用户名比较相等性和哈希值.然后,您可以在类上挂起其他可变数据 - "用户已登录"等.由于这不会影响相等性或散列,因此将其用作字典中的键是可能且完全有效的.这在Python中并不常见; 我只是指出它,因为有几个人声称密钥需要"不可变",这只是部分正确.不过,我已经多次使用C++地图和集合.

  • 你的困惑超出了我的帮助能力. (4认同)

tzo*_*zot 7

正如gnibbler在评论中提出的那样,Guido有一个未被完全接受/赞赏的观点:"列表用于同类数据,元组用于异构数据".当然,许多反对者认为这意味着列表的所有元素应该是相同的类型.

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

blue= 0, 0, 255
alist= ["red", "green", blue]
Run Code Online (Sandbox Code Playgroud)

请注意,我认为alist是同质的,即使是type(alist [1])!= type(alist [2]).

如果我可以改变元素的顺序,我的代码中不会出现问题(除了假设,例如"它应该被排序"),那么应该使用一个列表.如果不是(就像blue上面的元组中那样),那么我应该使用一个元组.


Mat*_*ela 6

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

a = [1,1,1]
doWork(a)
Run Code Online (Sandbox Code Playgroud)

来电者没有价值的保证一个电话后.然而,

a = (1,1,1)
doWorK(a)
Run Code Online (Sandbox Code Playgroud)

现在,作为调用者或此代码的读者,您知道a是相同的.您可以随时为此场景制作列表的副本并传递它,但现在您正在浪费周期而不是使用更具语义意义的语言构造.

  • 这是元组的一个非常次要的属性。在很多情况下,您想要将可变对象传递给函数而不对其进行修改,无论它是预先存在的列表还是其他某个类。Python 中没有“const 参数引用”的概念(例如,C++ 中的 const foo &amp;)。如果恰好使用元组很方便,元组恰好会给你这个,但是如果你从你的调用者那里收到了一个列表,你真的要在将它传递到其他地方之前将它转换为元组吗? (2认同)