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

Sha*_* Xu 289 python tuples list-comprehension dictionary-comprehension set-comprehension

众所周知,有列表理解,比如

[i for i in [1, 2, 3, 4]]
Run Code Online (Sandbox Code Playgroud)

并且有字典理解,比如

{i:j for i, j in {1: 'a', 2: 'b'}.items()}
Run Code Online (Sandbox Code Playgroud)

(i for i in (1, 2, 3))
Run Code Online (Sandbox Code Playgroud)

将最终成为一个发电机,而不是tuple理解.这是为什么?

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

Mar*_*ers 413

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

tuple(i for i in (1, 2, 3))
Run Code Online (Sandbox Code Playgroud)

但括号已被用于..生成器表达式.

  • 列表或集合或字典理解只是使用输出特定类型的生成器表达式的语法糖.`list(i for i in(1,2,3))`是一个生成表达式,输出一个列表,`set(i for i in(1,2,3))`输出一个集合.这是否意味着不需要理解语法?也许不是,但它非常方便.对于极少数情况,你需要一个元组,生成器表达式会很明显,并且不需要发明另一个支架或支架. (73认同)
  • 如果你关心性能,使用理解和使用构造函数+生成器之间的区别不仅仅是微妙的.与使用传递给构造函数的生成器相比,理解导致更快的构造.在后一种情况下,您正在创建和执行函数,并且函数在Python中很昂贵.`[事物中的东西]`比`list(事物中的东西)`更快地构造一个列表.元组理解不会毫无用处; `tuple(事物中的东西)`有延迟问题和`元组(事物中的东西))`可能有内存问题. (18认同)
  • 答案显然是因为元组语法和括号不明确 (15认同)
  • 通过这个论证,我们可以说列表理解也是不必要的:`list(i for i in(1,2,3))`.我真的认为这只是因为没有一个干净的语法(或者至少没有人想到过) (12认同)
  • @MartijnPieters,你能否重写`列表或集合或词典理解只是使用生成器表达式的语法糖?这会导致[混淆](/sf/ask/3641848451/)让人们看到这些*等同*意味着结束.它不是技术上的语法糖,因为这些过程实际上是不同的,即使最终产品是相同的. (6认同)
  • 相关:[Python 3 中的列表推导式是“列表(生成器表达式)”的语法糖吗?](/sf/ask/2106744601/经理-语法-糖-for-listgenerator-表达式-在pyth中) (3认同)
  • @MartijnPieters 想指出 `list(generator expression)` 与 `[generator expression]` 并不完全相同:http://www.thecodingforums.com/threads/generator-expressions-vs-comprehensions.723967/ (2认同)
  • @MartijnPieters 好点,尽管前几天我在尝试编写自己的 `zip` 函数作为学习 Python 的练习时偶然发现了它。不用说,这让我很困惑,为什么 `tuple(gen exp)` 失败了,但 `tuple([gen exp])` 工作得很好。无论如何,很高兴在这里注意到`StopIteration` 将被生成器表达式/推导式吞噬,但会传播到列表/集合/字典推导式之外。 (2认同)
  • @jpp:这是*评论*,而不是我的回答.评论通常不可编辑.从技术上讲,我可以编辑我的,但仅仅因为我是一名主持人.我支持我的评论,因为*语法非常接近.装饰器也是语法糖,它们的实现方式与它们替换的语法有很大不同,所以这不是一个孤立的例子.我不相信混淆的一个例子等于一般混乱. (2认同)

che*_*ner 68

Raymond Hettinger(Python核心开发人员之一)在最近的一条推文中有关于元组的说法:

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

这(对我来说)支持这样的想法,即如果序列中的项目足够相关以由生成器生成,那么它应该是一个列表.虽然元组是可迭代的,看起来就像一个不可变列表,但它实际上是C语言的Python等价物:

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

struct foo x = { 3, 'g', 5.9 };
Run Code Online (Sandbox Code Playgroud)

变成了Python

x = (3, 'g', 5.9)
Run Code Online (Sandbox Code Playgroud)

  • 虽然通常使用列表时使用元组通常是一个很好的理由,但是不可移植属性很重要.例如,如果你有一个5个数字的列表,你想用它作为dict的关键,那么元组就是你要走的路. (21认同)
  • 我认为 freezeset 和 set 之间的关系类似于元组和列表的关系。它与异质性无关,更多地与不变性有关——冻结集和元组可以成为字典的键,而列表和集合由于其可变性而不能。 (3认同)
  • 这是 Raymond Hettinger 提供的一个很好的建议。我仍然想说,有一个将元组构造函数与生成器一起使用的用例,例如通过迭代您有兴趣转换为元组记录的属性,将另一个可能更大的结构解包为较小的结构。 (2认同)
  • @dave在这种情况下,您可以只使用`operator.itemgetter`。 (2认同)
  • 还有一种常见情况是使用生成器生成类似结构的内容:处理 CSV 等文本记录。这通常写为“line_values = tuple(int(x.trim()) for x in line.split(','))”。正如其他人所指出的,在这里使用元组构造函数而不是理解会对性能产生影响,并且解析这种类型的大型数据集是您真正关心性能的情况。 (2认同)

czh*_*heo 47

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

*(x for x in range(10)),
Run Code Online (Sandbox Code Playgroud)

  • @RyanH。你需要在最后加一个逗号。 (12认同)
  • 注意:作为一个实现细节,这基本上与`tuple(list(x for x in range(10)))`([代码路径相同](https://github.com/python/cpython)相同/blob/3.7/Python/ceval.c#L2283),它们都构建了一个`list`,唯一的区别是最后一步是从`list`创建一个`tuple`并扔掉`列表`当需要`tuple`输出时).意味着你实际上并没有避开一对临时工. (4认同)
  • 我在 Python 3.7.3 中尝试此操作,但 `*(x for x in range(10))` 不起作用。我得到“语法错误:不能在此处使用加星号的表达式”。然而“tuple(x for x in range(10))”是有效的。 (3认同)
  • 这很棒(而且可行),但是我找不到它记录的任何地方!你有链接吗? (2认同)
  • 为了扩展@ShadowRanger的评论,[这里是一个问题](/sf/ask/3330139151/),他们显示splat +实际上,元组文字语法比将生成器表达式传递给元组构造函数要慢很多。 (2认同)

Inb*_*ose 23

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

创建元组后,无法附加,扩展或分配.修改元组的唯一方法是,如果其中一个对象本身可以分配给(是非元组容器).因为元组只持有对那种物体的引用.

另外 - 元组有自己的构造函数tuple(),你可以给任何迭代器.这意味着要创建一个元组,你可以这样做:

tuple(i for i in (1,2,3))
Run Code Online (Sandbox Code Playgroud)

  • 在某些方面我同意(关于它不是必要的,因为列表会这样做),但在其他方面我不同意(关于推理因为它是不可变的).在某些方面,理解不可变对象更有意义.谁做了`lst = [x for x in ...]; x.append()`? (9认同)
  • @mgilson如果一个元组是不可变的,那意味着底层实现*不能"生成"一个元组("一代"意味着一次构建一个元组).不可变的意味着你不能通过改变一件有3件而构建一件4件.相反,您通过构建列表来实现元组"生成",这是为生成而设计的,然后将元组构建为最后一步,并丢弃列表.语言反映了这一现实.将元组视为C结构. (2认同)
  • 尽管推导式的语法糖对元组起作用是合理的,因为在推导式返回之前您不能使用元组。实际上,它并不像可变的,而是元组理解可能表现得更像字符串附加。 (2认同)

小智 20

正如另一张海报所macm提到的,从发电机创建元组的最快方法是tuple([generator]).


绩效比较

我的python版本:

$ python3 --version
Python 3.6.3
Run Code Online (Sandbox Code Playgroud)

因此,除非性能不是问题,否则应始终从列表推导中创建元组.

  • 注意:listcomp的`tuple`需要基于最终`tuple`和`list`的组合大小的峰值内存使用量.genexpr的`tuple`虽然较慢,但它的意思是你只支付最终的`tuple`,没有临时`list`(genexpr本身占据大致固定的内存).通常没有意义,但是当涉及的尺寸很大时,它可能很重要. (4认同)
  • 当我尝试使用更大数据的列表理解中的元组与生成器中的元组(大致说范围(1_000_000))时,您会看到生成器中的元组将花费更少的时间,尽管它不是那么重要,但您最终会节省大小和数据较大时的时间 (2认同)

mgi*_*son 12

我最好的猜测是,他们用完了括号,并且认为它不足以增加一个"丑陋"的语法......

  • @uchuugaka值得注意的是,`{*()}`虽然丑陋但是作为一个空集合文字! (2认同)
  • 啊。从美学的角度来看,我认为我偏向于 `set()` :) (2认同)
  • @QuantumMechanic:是的,这就是重点;解包概括使得空的“集合文字”成为可能。请注意,`{*[]}` 严格低于其他选项;空字符串和空 `tuple` 是不可变的,是单例,因此不需要临时构建空 `set`。相比之下,空的 `list` 不是单例,所以你实际上必须构造它,用它来构建 `set`,然后销毁它,失去独眼猴算子提供的任何微不足道的性能优势。 (2认同)

小智 8

无法像列表那样有效地附加元组.

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

那和你现在做的一样:元组([理解])


归档时间:

查看次数:

91529 次

最近记录:

5 年,11 月 前