我已经阅读了一些Python教程(比如说,深入了解Python),以及python.org上的语言参考——我不明白为什么该语言需要元组。
与列表或集合相比,元组没有任何方法,如果必须将元组转换为集合或列表才能对其进行排序,首先使用元组有什么意义?
不变性?
为什么有人关心变量在内存中的位置是否与最初分配的位置不同?在python中,关于不可变性的整个业务似乎被过分强调了。
在C/C++中,如果我分配一个指针并指向某个有效的内存,我不关心地址在哪里,只要它在使用之前不是空的。
每当我引用该变量时,我不需要知道指针是否仍指向原始地址。我只是检查一下空值并使用它(或不使用)。
在python中,当我分配一个字符串(或tuple)时,将其分配给x,然后修改该字符串,为什么我关心它是否是原始对象?只要变量指向我的数据,这就是最重要的。
1 2 3 4 5 6
| >>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167 |
x仍然引用我想要的数据,为什么有人需要关心它的ID是相同的还是不同的?
- 您注意到易变性的错误方面:"ID是相同的还是不同的"只是一个副作用;"以前指向同一对象的其他引用所指向的数据现在是否反映更新"是至关重要的。
不可变的对象可以允许实质性的优化;这大概是为什么字符串在Java中也是不可变的,它是单独开发的,但在Python的同时,几乎所有的东西在真正的函数语言中都是不可变的。
尤其是在Python中,只有不可变的才是可哈希的(因此,集合的成员或字典中的键)。同样,这也提供了优化,但不仅仅是"实质性的"(设计一个体面的哈希表来存储完全可变的对象是一个噩梦——要么在散列之后立即复制所有内容,要么在最后一次引用对象后检查对象的哈希是否发生了变化的噩梦中抬起了丑陋的头)。
优化问题示例:
1 2 3 4
| $ 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 |
- 我不喜欢优化参数,因为我从未见过元组和列表在性能上有明显的区别。不过,第二点我会加1。
- @音乐怪胎,看我刚才做的编辑,构建一个元组比构建等价列表快7.6倍以上——现在你不能说你"从未见过显著的差异",除非你对"显著"的定义是真正独特的……
- @亚历克斯:I have seen improvements of some microseconds,but it really going to change the performance of an entire application?我对此表示怀疑。我曾经重写了一个实时应用程序的一部分,该应用程序大量使用元组来代替列表,因此没有明显的性能下降,所以我支持我的原始语句。
- @音乐怪人我认为你误用了"过早的优化是万恶之源"。在应用程序中过早地进行优化有很大的区别(例如,说"元组比列表更快,所以我们将只在所有应用程序中使用元组!")做基准。Alex的基准测试很有洞察力,并且知道构建元组比构建列表更快,这可能有助于我们将来的优化操作(当它确实需要时)。
- @亚历克斯,"构建"元组真的比"构建列表"更快吗?还是我们看到了Python运行时缓存元组的结果?在我看来是后者。
- @色氨酸,不可变性允许对文字进行预计算和"仅仅获取"文字-它甚至不仅仅是缓存(检查w/dis.dis!).@virgil,构建一个非文字元组的速度只比构建一个列表快2-3倍——在本例中,其余的优势来自于使用文字。@MusicFreak,你似乎对Knuth有着深深的误解:他从不提倡使用64位浮点代替32位整数,因为在一个应用程序中,你无法"注意"差异(例如"注意"这样的主观标准,事实上,他会直接拒绝!-)
- 支持取数参数,python -mtimeit -s"import random""(random.random(), random.random())"对于列表是0.28 usec,对于元组是0.23 usec。
- @Acoolie,这完全是由random电话控制的(试着这样做,你会看到的!)所以不是很重要。尝试python -mtimeit -s"x=23""[x,x]",你会看到构建tuple和构建列表的速度加快了2-3倍。
- @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")以删除该组件)
- 突然间,我有很多"常量"序列想成为元组
- 对于任何想知道的人来说——我们可以通过从列表切换到元组来节省一个多小时的数据处理时间。
上面的所有答案都没有指出元组与列表的真正问题,而这对于Python来说,许多新手似乎并不完全理解。
元组和列表有不同的用途。列表存储同质数据。您可以也应该有这样的列表:
1
| ["Bob","Joe","John","Sam"] |
正确使用列表的原因是因为这些都是同质的数据类型,特别是人名。但是,像这样的列表:
1
| ["Billy","Bob","Joe", 42] |
那张单子是一个人的全名和年龄。这不是一种数据。存储信息的正确方法是使用元组或对象。假设我们有一些:
1
| [("Billy","Bob","Joe", 42), ("Robert","","Smith", 31)] |
元组和列表的不变性和可变性并不是主要区别。列表是同类项目的列表:文件、名称、对象。元组是一组不同类型的对象。它们有不同的用途,许多Python编码人员滥用tuple的用途列表。
请不要这样。
编辑:
我认为这篇博文解释了为什么我认为这比我做的更好:http://news.e-scribe.com/397
- 我觉得你有一个至少我不同意的愿景,不认识其他人。
- 我也强烈反对这个答案。数据的同质性与您应该使用列表还是元组完全无关。在python中,没有任何东西显示出这种区别。
- 您可以将所有内容和任何内容存储在列表中,以及所有本机Python容器中。
- 几年前,吉多也提出了这一点。aspn.activestate.com/aspn/mail/message/python-list/1566320
- 这在几乎任何其他语言中都是一个有元组的有效点,但它不适用于Python。语言本身并不限制列表的使用仅限于同构类型的数据,因此您可以自由地以任何方式使用它们。如果它们是以这种特定的方式使用的,为什么语言不强制使用呢?不管怎样,你的回答都不是真的有效。
- 尽管guido(python的设计者)打算将列表用于异构数据的同构数据和元组,但事实上,语言并没有强制实现这一点。因此,我认为这种解释比其他任何解释都更像是一种风格问题。在许多人的典型用例中,列表往往像数组,元组则像记录。但是,如果列表更适合他们的问题,这不应该阻止人们使用异类数据列表。正如Python禅宗所说:实用胜过纯洁。
- +1:纯粹是为了引起我对元组和列表的理解。到现在为止,我对他们的了解可能太低了。
- 我修改了我的声明,我要投票给你(我本来没有投票给你)。我从你的回答中学到了一些新的东西。仍然不完全同意,但你提出了一个有趣的观点。
- 这里接受的答案有一个类似的观点:stackoverflow.com/questions/1708510/…
- @格伦,你基本上错了。元组的主要用途之一是作为一种复合数据类型来存储多个相关的数据片段。事实上,您可以迭代一个元组并执行许多相同的操作,但这并不能改变这一点。(参考文献认为,许多其他语言中的元组与列表中的元组不具有相同的可重复特性)
- 不,这与同质性无关。如果需要修改不同的数据类型,使用数组是完全正常的;如果需要不可变的数据类型,则使用元组是同种数据类型。items = []; items.append("text"); items.append(100); for i in items: print i是完全有效的,一个元组根本不能做到这一点。
- 格伦,这是有效的代码,但样式不正确。如果索引很重要,请使用元组;如果索引没有引用特定元素,请使用列表。如果需要可变,那么创建可变元组或其他。
- 我投了这个票,有一个原因。"正确使用列表的原因是因为这些都是同种类型的数据,"存储信息的正确方法要么是在元组中",这两种断言都是您没有给出理由的。据我所知,一个等价的答案可以颠倒元组和列表,并传递相同数量的信息。
- Therms:之所以有效,是因为这就是语言的设计和实现方式。看上面,它链接到guido谈论的完全相同的事情。元组并不特定于Python,在更常用的函数语言中,它们的区别更明显,但在Python中也有相同的区别,只是没有强制执行。一个经典的例子:数据库中的行不是列表。元组是将相关对象分组为单个对象,如数据库行。一个数据库,那么是一个元组列表,而不是一个列表的元组。
- 这个答案解释不了什么。想象有人告诉你把袜子放在鞋子前面,你问他们为什么。他们回答说:"这是正确的方式,加上莎士比亚、爱因斯坦和伦勃朗都是这样做的,你不比他们聪明吗?"在基本逻辑的层面上,这种回答失败了:(1)提出问题,一种谬论,其中一个断言被用作同一断言是正确的证据;(2)权威的论证,使用专家的观点作为证据,这些观点是正确的。专家持有观点的理由,而不是他持有观点的事实。
- @CHPWN:"之所以有效,是因为这就是语言的设计和实现方式。"这句话说明了为什么你的答案是错误的。python允许列表是异构的,元组是同质的。语言实现与您的答案相矛盾。有很好的理由把你的建议作为一种风格来遵循。利用好的理由,而不是坚持坏的理由。
- 列表基本上是链表/向量(数据和长度可变),元组基本上是常量数组(数据和长度不可变)。两者都可以而且应该能够拥有异构数据,以便能够解决现实世界中的问题。guido经常提到很多关于python的事情,这些事情在现实世界中毫无意义,不管它是不是语言的创造者。语言创建者并非万无一失;我们通常会做出完全遗忘的假设。
- @数据库行中的grant字段是可编辑的;因此它们与元组完全不同。如果有的话,相应的python基对象将是dict(具有可编辑数据的命名字段)。数据库是dict的列表——一个包含可编辑对象的可编辑对象。根据实现细节,列表或dict的dict列表可能更快。
- 列表还可以包含不同类型的对象!
- 我投了反对票,因为这完全不能解决不可变的问题。
if I must convert a tuple to a set or list to be able to sort them, what's the point of using a tuple in the first place?
在这种情况下,可能没有什么意义。这不是问题,因为这不是您考虑使用元组的情况之一。
正如您所指出的,元组是不可变的。不可变类型的原因适用于元组:
- 复制效率:不复制不可变对象,您可以对其进行别名(将变量绑定到引用)
- 比较效率:当使用引用复制时,可以通过比较位置而不是内容来比较两个变量
- 实习:您最多需要存储一个不可变值的副本
- 不需要同步对并发代码中不可变对象的访问
- 常量正确性:不允许更改某些值。这(对我来说)是不可变类型的主要原因。
请注意,特定的Python实现可能不会使用上述所有特性。
字典键必须是不可变的,否则更改键对象的属性会使基础数据结构的不变量无效。因此,元组可以潜在地用作键。这是常量正确性的结果。
另请参见"介绍tuples",从dive into python开始。
- id((1,2,3))==id((1,2,3))为假。不能只通过比较位置来比较元组,因为不能保证它们是通过引用复制的。
- @格伦:请注意限定性的注释"当您使用副本时参考"。虽然编码人员可以创建自己的实现,但元组的引用复制在很大程度上是解释器/编译器的问题。我主要是指如何在平台级别实现==。
- @格伦:还要注意,参照复制不适用于(1,2,3) == (1,2,3)中的元组。这更像是一个实习的问题。
- 很好的回答。+ 1
- 正如我说得相当清楚,不能保证它们是通过引用复制的。tuples不在python中被拘禁;这是一个字符串概念。
- 就像我说得很清楚:我不是说程序员通过比较位置来比较元组。我说的是平台可以的可能性,它可以通过参考来保证复制。此外,interning可以应用于任何不可变类型,而不仅仅是字符串。主要的python实现可能不是不可变的类型,但是python具有不可变的类型这一事实使interning成为一个选项。
- 另外,id((1,2,3)) != id((1,2,3))证明了元组没有被内部化;它没有证明元组没有引用copy。
- 为了说明为什么Python有元组,我们必须戴上语言设计师的帽子,同时考虑语言用户和实现者。元组是由语言实现者实现的;因此,通过引用复制、严格的相等性和元组的内接都是实现者所关心的,而不是Python用户。更强大的语言允许用户进行扩展,因此用户可以将这些特性应用于自己的数据类型,但这与元组无关。这些实现细节提供的效率,以及同步和常量正确性,与用户相关。
有时我们喜欢用对象作为字典键
值得一提的是,最近tuples(2.6+)增长了index()和count()方法。
- +1:作为字典键的可变列表(或可变集或可变字典)无法工作。所以我们需要不变的列表("元组")、冻结集和…好。。。我想是一本冻结的字典。
我总是发现对于相同的基本数据结构(数组)有两个完全不同的类型是一个笨拙的设计,但实际上并不是一个真正的问题。(每种语言都有自己的缺点,包括Python,但这并不重要。)
Why does anyone care if a variable lives at a different place in memory than when it was originally allocated? This whole business of immutability in Python seems to be over emphasized.
这些是不同的东西。易变性与它存储在内存中的位置无关;它意味着它指向的东西是不能改变的。
python对象在创建后不能更改位置,是否可变。(更准确地说,id()的值不能更改——实际上是一样的。)可变对象的内部存储可以更改,但这是隐藏的实现细节。
1 2 3 4 5 6
| >>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167 |
这不是修改("变异")变量;而是创建一个同名的新变量,并丢弃旧变量。与突变操作相比:
1 2 3 4 5 6 7 8
| >>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L |
正如其他人指出的,这允许使用数组作为字典的键,以及其他需要不可变的数据结构。
请注意,字典的键不一定是完全不可变的。只有用作密钥的部分需要是不可变的;对于某些用途,这是一个重要区别。例如,您可以有一个表示用户的类,该类通过唯一用户名比较相等性和哈希值。然后可以在类上挂起其他可变数据——"用户已登录"等。因为这不会影响相等性或哈希值,所以将其用作字典中的键是可能的,也是完全有效的。这在Python中并不常见;我只是指出了它,因为有几个人声称密钥必须是"不可变的",这只是部分正确的。不过,我已经多次使用C++映射和集合。
- > > > > > = [ 2 ](a)3084599212l ID > > > > [ 1 ] = 5 > > > [ 1,5,3)> > > ID(A)3084599212l你只是一mutable修饰的数据类型,它是不相关的,这让意识原来的问题。X ="Hello"标识(x)x ="12345"再见ID(X)65432 WHO cares如果它是一个新的对象或不。只要X点的数据,我assigned,这一切matters。
- "你真好混乱之外的能力帮助你。
- + 1的pointing了混乱的子问题,这似乎是"开源"的主要困难和perceiving值的元组。
- 如果我能,另一个用于pointing + 1的真正钥匙了,标题为"对象是否是或不是hashable(docs.python.org / glossary.html #热- hashable)。
正如Gnibbler在评论中所说,Guido的观点没有被完全接受/认可:"列表是针对同质数据的,元组是针对异构数据的"。当然,许多反对者将这解释为列表中的所有元素都应属于同一类型。
我喜欢以不同的方式看待它,这与其他人过去也一样:
1 2
| blue= 0, 0, 255
alist= ["red","green", blue] |
注意,我认为alist是同构的,即使是类型(alist[1])!=类型(alist[2])。
如果我可以更改元素的顺序,并且代码中没有问题(除了假设,例如"应该排序"),那么应该使用一个列表。如果不是(像上面的tuple blue中那样),那么我应该使用tuple。
- 如果我能,我能投票,这个答案的15倍。这是关于我的感觉如何exactly元组。
它们很重要,因为它们保证调用方传递的对象不会发生变化。如果你这样做:
呼叫方不保证呼叫后的值。然而,
现在,作为调用者或代码的读者,您知道a是相同的。对于这个场景,您可以一直复制列表并传递它,但是现在您正在浪费循环,而不是使用更具语义意义的语言构造。
- 这是元组的一个非常次要的属性。在很多情况下,您有一个可变的对象想要传递给一个函数而不修改它,不管它是一个预先存在的列表还是其他类。在Python中没有"引用const参数"的概念(如const fo&c++)。如果使用元组很方便的话,元组正好给了你这个,但是如果你从调用者那里收到了一个列表,你真的要在把它传递到其他地方之前将它转换成元组吗?
- 我同意你的看法。一个元组和一个const关键字不一样。我的观点是,元组的不变性为代码的读者带来了附加的意义。在这样一种情况下,两种方法都会奏效,而且您的期望是,使用元组不应该改变,这将为读者增加额外的含义(同时确保它也是如此)。
- a=[1,1,1]dowork(a)如果dowork()定义为def dowork(arg):arg=[0,0,0]在列表或元组上调用dowork()的结果相同
您的问题(以及后续注释)集中在id()是否在赋值期间更改。关注这个后续效应的不可变对象替换和可变对象修改之间的差异,而不是差异本身,可能不是最好的方法。
在继续之前,请确保下面演示的行为是您对Python的期望。
1 2 3 4 5 6 7
| >>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2 |
在这种情况下,A2的内容发生了变化,即使只有A1分配了一个新值。与以下内容形成对比:
1 2 3 4 5 6 7
| >>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1 |
在后一种情况下,我们替换了整个列表,而不是更新其内容。对于不可变类型(如元组),这是唯一允许的行为。
为什么这很重要?假设你有口述:
1 2 3 4 5 6 7 8
| >>> 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'} |
使用元组,字典可以安全地将其键从"out-out-under-it"更改为哈希值不同的项。这对于实现有效性至关重要。
- 为人有了pointed,immutability!hashability =。在所有的元组,可以被用来作为字典的键:{([ 1 ],[ 2 ]:"值")} fails因为"mutable列表和元组可以altered,但{((1),(2):"值")}是好的。
- 是的,这是真的,但我不确定那是德国人的区别的问题是asked。
- "k.nicholas,编辑你的代码approved来改变这样一种方式,这是一assigning为整数,不一元,在所有的制作的操作失败。之后,他们不能进行测试,可能有新的transcript actually是可能的。correctly - identified问题,肯定的;无效的解决方案。
- "michaelpuckettii,likewise,见上面。
你可以在这里看到一些关于这个的讨论