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)
However, when I
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)}
There are no "tuple comprehensions" in Python. This:
x for x in range(5)
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.
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
Now, doing *
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.
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.
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)}
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 ( at 0x10f730ed0, file "", line 1>)
2 LOAD_CONST 1 ('')
4 MAKE_FUNCTION 0
6 LOAD_CONST 5 ((1, 2, 3))
8 GET_ITER
10 CALL_FUNCTION 1
12 RETURN_VALUE