Python3.5 元组理解真的有这么有限吗?

2022-01-20 00:00:00 python python-3.x tuples

问题描述

我一直很喜欢 Python3.5 中添加的元组推导:

I've been loving the tuple comprehensions added to Python3.5:

In [128]: *(x for x in range(5)),
Out[128]: (0, 1, 2, 3, 4)

但是,当我尝试直接 return 元组推导时,我得到一个错误:

However, when I try to return a tuple comprehension directly I get an error:

In [133]: def testFunc():
     ...:     return *(x for x in range(5)),
     ...: 
  File "<ipython-input-133-e6dd0ba638b7>", line 2
    return *(x for x in range(5)),
           ^
SyntaxError: invalid syntax    

这只是一个小小的不便,因为我可以简单地将元组推导式分配给一个变量并返回该变量.但是,如果我尝试将元组推导式放入字典推导式中,我会得到同样的错误:

This is just a slight inconvenience since I can simply assign the tuple comprehension to a variable and return the variable. However, if I try and put a tuple comprehension inside a dictionary comprehension I get the same error:

In [130]: {idx: *(x for x in range(5)), for idx in range(5)}
  File "<ipython-input-130-3e9a3eee879c>", line 1
    {idx: *(x for x in range(5)), for idx in range(5)}
          ^
SyntaxError: invalid syntax

我觉得这有点问题,因为在某些情况下,理解对性能很重要.

I feel like this is a bit more of a problem since comprehsions can be important for performance in some situations.

在这些情况下使用字典和列表推导没有问题.有多少其他情况是元组推导式在其他情况下不起作用?还是我用错了?

I have no problem using dictionary and list comprehensions in these situations. How many other situations is the tuple comprehension not going to work when others do? Or perhaps I'm using it wrong?

这让我想知道,如果它的用途如此有限,或者我做错了什么,这有什么意义?如果我没有做错什么,那么创建一个足够通用的元组以与列表和字典理解相同的方式使用的最快/最 Pythonic 的方法是什么?

It makes me wonder what the point was if it's use is so limited or perhaps I am doing something wrong? If I'm not doing something wrong then what is the fastest/most pythonic way to create a tuple that is versitile enough to be used in the same way as list and dictionary comprehensions?


解决方案

TLDR: 如果你想要一个元组,传递一个生成器表达式给 tuple:

TLDR: If you want a tuple, pass a generator expression to tuple:

{idx: tuple(x for x in range(5)) for idx in range(5)}

<小时>

Python 中没有元组推导".这个:


There are no "tuple comprehensions" in Python. This:

x for x in range(5)

是一个生成器表达式.在其周围添加括号仅用于将其与其他元素分开.这与 (a + b) * c 中的相同,也不涉及元组.

is a generator expression. Adding parentheses around it is merely used to separate it from other elements. This is the same as in (a + b) * c, which does not involve a tuple either.

* 符号用于iterator 打包/解包.生成器表达式恰好是可迭代的,因此可以解包.但是,必须有一些东西可以将可迭代的 解压缩到.例如,还可以将列表解包到赋值的元素中:

The * symbol is for iterator packing/unpacking. A generator expression happens to be an iterable, so it can be unpacked. However, there must be something to unpack the iterable into. For example, one can also unpack a list into the elements of an assignment:

*[1, 2]                         # illegal - nothing to unpack into
a, b, c, d = *[1, 2], 3, 4      # legal - unpack into assignment tuple

现在,执行 *<iterable>,* 解包与 , 元组文字结合起来.这不是在所有情况下都可用,但是 - 分离元素可能优先于创建元组.例如,[*(1, 2), 3] 中的最后一个 , 分隔,而在 [(*(1, 2), 3)] 它创建一个元组.

Now, doing *<iterable>, combines * unpacking with a , tuple literal. This is not useable in all situations, though - separating elements may take precedence over creating a tuple. For example, the last , in [*(1, 2), 3] separates, whereas in [(*(1, 2), 3)] it creates a tuple.

在字典中,, 是模棱两可的,因为它用于分隔元素.比较 {1: 1, 2: 2} 并注意 {1: 2,3} 是非法的.对于 return 语句,它将来可能会实现.

In a dictionary the , is ambiguous since it is used to separate elements. Compare {1: 1, 2: 2} and note that {1: 2,3} is illegal. For a return statement, it might be possible in the future.

如果你想要一个元组,你应该在可能有歧义的时候使用 () - 即使 Python 可以处理它,否则很难为人类解析.

If you want a tuple, you should use () whenever there might be ambiguity - even if Python can handle it, it is difficult to parse for humans otherwise.

当您的源代码是大型语句(例如生成器表达式)时,我建议显式转换为元组.比较您的代码的以下两个有效版本的可读性:

When your source is a large statement such as a generator expression, I suggest to convert to a tuple explicitly. Compare the following two valid versions of your code for readability:

{idx: tuple(x for x in range(5)) for idx in range(5)}
{idx: (*(x for x in range(5)),) for idx in range(5)}

请注意,list 和 dict 推导的工作方式也类似——它们实际上就像将生成器表达式传递给 listsetdict.它们主要用于避免在全局命名空间中查找 listsetdict.

Note that list and dict comprehensions also work similar - they are practically like passing a generator expression to list, set or dict. They mostly serve to avoid looking up list, set or dict in the global namespace.

我觉得这有点问题,因为在某些情况下,理解对性能很重要.

I feel like this is a bit more of a problem since comprehsions can be important for performance in some situations.

在幕后,生成器表达式和列表/字典/集合推导式都创建了一个短暂的函数.除非您已经对它们进行了概要分析和测试,否则您不应该依赖于性能优化的理解.默认情况下,使用最适合您的用例的内容.

Under the covers, both generator expressions and list/dict/set comprehensions create a short-lived function. You should not rely on comprehensions for performance optimisation unless you have profiled and tested them. By default, use whatever is most readable for your use case.

dis.dis("""[a for a in (1, 2, 3)]""")
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x10f730ed0, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_CONST               5 ((1, 2, 3))
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

相关文章