为什么Python中没有元组理解?


337

众所周知,列表理解

[i for i in [1, 2, 3, 4]]

并且有字典理解,例如

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

(i for i in (1, 2, 3))

最终将成为生成器,而不是tuple理解力。这是为什么?

我的猜测是a tuple是不可变的,但这似乎并不是答案。


15
还有一个固定的理解力-看起来很像字典理解力……
mgilson

3
您的代码中有一个语法错误:{i:j for i,j in {1:'a', 2:'b'}}应该是{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose

@InbarRose感谢您指出
Shady Xu

仅出于后代的目的,在Python聊天室中
Inbar Rose

Answers:


466

您可以使用生成器表达式:

tuple(i for i in (1, 2, 3))

但是对于...生成器表达式,已经使用了括号。


15
通过这种说法,我们可以说列表理解也是不必要的: list(i for i in (1,2,3))。我真的认为这仅仅是因为没有一种清晰的语法(或者至少没有人想到过这种语法)
mgilson 2013年

79
列表或集合或dict的理解只是语法糖,可以使用输出特定类型的生成器表达式。list(i for i in (1, 2, 3))是生成器表达式,用于输出列表,set(i for i in (1, 2, 3))输出集合。这是否意味着不需要理解语法?也许不是,但是非常方便。在极少数情况下,您需要一个元组,生成器表达式可以做到,很清楚,并且不需要发明另一个花括号或括号。
马丁·彼得斯

16
答案显然是因为元组语法和括号是模棱两可的
Charles Salvia

19
如果您关心性能,那么使用理解和使用构造函数+生成器之间的区别会非常微妙。与使用传递给构造函数的生成器相比,理解可以加快构建速度。在后一种情况下,您要创建和执行函数,而函数在Python中是昂贵的。 [thing for thing in things]构造列表的速度比快得多list(thing for thing in things)。元组的理解不是没有用的。tuple(thing for thing in things)有延迟问题,tuple([thing for thing in things])可能有内存问题。
贾斯汀·特纳Arthur

9
@MartijnPieters,您可能会改写A list or set or dict comprehension is just syntactic sugar to use a generator expression吗?人们将这些视为达到目的的等效手段,这引起了混乱。从技术上讲,这不是语法糖,因为即使最终产品相同,过程也实际上是不同的。
jpp

77

Raymond Hettinger(Python核心开发人员之一)在最近的一条推文中曾这样说过元组:

#python提示:通常,列表用于循环;结构的元组。列表是同质的;元组异构。列出可变长度。

(对我来说)支持这样的想法:如果序列中的项目相关性足以由生成器生成,那么它应该是一个列表。尽管元组是可迭代的,并且看起来只是一个不可变的列表,但它实际上与C结构的Python等效:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

成为Python

x = (3, 'g', 5.9)

26
但是,不变性属性可能很重要,并且通常是在通常使用列表时使用元组的一个很好的理由。例如,如果您有一个5个数字的列表要用作字典的键,那么元组就是要走的路。
pavon,2015年

那是Raymond Hettinger的一个不错的提示。我仍然要说有一个用例,它可以将元组构造函数与生成器一起使用,例如,通过迭代您有兴趣转换为元组记录的attrs,将另一个可能更大的结构分解为一个较小的结构。
戴夫

2
@dave operator.itemgetter在这种情况下可以使用。
chepner '16

@chepner,我知道了。这很接近我的意思。它确实会返回一个可调用对象,因此,如果我只需要使用一次,而与直接使用相比,没有太大的获胜机会,就可以这样做tuple(obj[item] for item in items)。以我为例,我将其嵌入到列表推导中以创建元组记录列表。如果我需要在整个代码中重复执行此操作,则itemgetter看起来很棒。也许itemgetter会更惯用?
戴夫

我看到Frozenset和set之间的关系类似于元组和list的关系。与其说是异质性,不如说是不变性-冻结集和元组可以是字典的键,而列表和集合由于其可变性而不能。
polyglot

56

从Python 3.5开始,您还可以使用splat *解包语法来解压缩生成器表达式:

*(x for x in range(10)),

2
这很棒(而且很有效),但是我找不到它记录的任何地方!你有链接吗?
felixphew

8
注意:作为一个实现细节,这是基本相同做tuple(list(x for x in range(10)))的代码路径是相同的,与他们两个构建list,唯一的区别是最后一步是建立一个tuplelist和扔掉listtuple输出是必须的)。意味着您实际上并没有避开一对临时工。
ShadowRanger

4
为了扩展@ShadowRanger的评论,这是一个问题,他们表明splat + tuple文字语法实际上比将生成器表达式传递给tuple构造函数要慢得多。
Lucubrator

我正在Python 3.7.3中尝试此操作,*(x for x in range(10))但无法正常工作。我懂了SyntaxError: can't use starred expression here。但是tuple(x for x in range(10))有效。
Ryan H.

4
@RyanH。您需要在最后加上逗号。
czheo

27

正如另一位发帖人macm所述,从生成器创建元组的最快方法是tuple([generator])


性能比较

  • 清单理解:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
    
  • 来自列表理解的元组:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
    
  • 生成器中的元组:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
    
  • 打开包装的元组:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop
    

我的python版本

$ python3 --version
Python 3.6.3

因此,除非性能不是问题,否则应始终从列表理解中创建一个元组。


10
注意:tuplelistfin要求根据final tuple和的组合大小达到峰值内存使用量listtupleGenexpr的速度虽然较慢,但确实意味着您只需要为final tuple而不是临时list的付费(genexpr本身占用大致固定的内存)。通常没有意义,但是当涉及的大小很大时,这可能很重要。
ShadowRanger

25

理解通过循环或迭代项并将它们分配到容器中来工作,元组无法接收分配。

创建元组后,将无法对其进行追加,扩展或分配。修改元组的唯一方法是是否可以将其对象之一本身分配给它(是非元组容器)。因为元组仅持有对此类对象的引用。

另外-元组有自己的构造函数tuple(),您可以提供任何迭代器。这意味着要创建一个元组,您可以执行以下操作:

tuple(i for i in (1,2,3))

9
在某些方面,我同意(因为列表会做,所以没有必要),但在其他方面,我不同意(关于原因,因为它是不可变的)。在某些方面,对不可变对象有一个更全面的理解。谁lst = [x for x in ...]; x.append()呢?
mgilson

@mgilson我不确定这与我所说的有什么关系?
Inbar Rose

2
@mgilson,如果一个元组是不可变的,则意味着基础实现无法 “生成”一个元组(“生成”意味着一次构建一个元组)。不可变意味着您无法通过更改3个组成部分来构建4个组成部分。取而代之的是,通过构建一个列表(为生成而设计)来实现元组“生成”,然后作为最后一步来构建元组,并丢弃该列表。语言反映了这一现实。将元组视为C结构。
斯科特,

2
尽管理解的语法糖对元组起作用是合理的,但是因为在返回理解之前您不能使用元组。实际上,它的行为不像可变的,而是对元组的理解可以像字符串附加一样。
uchuugaka

12

我最好的猜测是,他们用完了括号,并认为这对警告添加“丑陋”语法没有足够的帮助...


1
尖括号未使用。
uchuugaka

@uchuugaka-不完全是。它们用于比较运算符。可能仍然可以毫不含糊地完成,但可能不值得付出努力……
mgilson

3
@uchuugaka值得一提的是{*()},尽管很丑陋,但却可以作为一个空集字面量!
MI Wright

1
啊。从美学的角度来看,我认为我很喜欢set():)
mgilson

1
@QuantumMechanic:是的,这就是重点;解包概括使空的“设置文字”成为可能。请注意,这{*[]}绝对不如其他选项。空字符串和Empty tuple是不变的,是单例,因此不需要临时来构造Empty set。相比之下,空list不是单例,因此您实际上必须构造它,使用它来构建set,然后销毁它,从而失去了单眼猴子运算符提供的任何琐碎的性能优势。
ShadowRanger

8

元组不能像列表一样有效地附加。

因此,元组理解将需要在内部使用列表,然后转换为元组。

那将与您现在所做的相同:tuple([comprehension])


3

括号不会创建元组。又名一个=(两个)不是元组。唯一的解决方法是一个=(两个)或一个=元组(两个)。因此,解决方案是:

tuple(i for i in myothertupleorlistordict) 

很好 几乎一样。
uchuugaka

-1

我相信这只是为了清楚起见,我们不想用太多不同的符号来使语言混乱。同样tuple也不需要理解,可以使用列表,而速度差异可以忽略不计,而不是像dict理解而不是list理解。


-2

我们可以从列表理解中生成元组。下一个将两个数字顺序加到一个元组中,并给出一个从0-9的列表。

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.