在Python列表推导式中解包元组(不能使用*运算符)

56

我想基于另一个列表创建一个列表,其中相同的值连续重复3次。

目前,我正在使用:

>>> my_list = [ 1, 2 ]
>>> three_times = []
>>> for i in range( len( my_list ) ):
...   for j in range( 3 ):
...     three_times.append( my_list[ i ] )
...
>>> print three_times
[1, 1, 1, 2, 2, 2]

但我希望能够用更符合Python风格的方式来实现,例如:

>>> my_list = [ 1, 2 ]
>>> three_times = []
>>> three_times = [ (value,) * 3 for value in my_list ]
>>> print(three_times)
[(1, 1, 1), (2, 2, 2)]

然而,我找不到拆包元组的方法。

类似于three_times = [ *( (value,) * 3 ) for value in my_list ]这样的语法对于拆包元组非常完美, 但这不是正确的语法。


我觉得这种次优的方法很符合Python的风格,即sum([[i] * 3 for i in [1,2]], []) - undefined
2个回答

55

在列表推导式中,你不能使用* 可迭代解包语法,这种语法只适用于函数调用和Python 3中的赋值操作。

如果你想使用列表推导式,只需将for循环串联在一起;你需要直接访问my_list中的值而不是生成索引:

[v for v in my_list for _ in range(3)]

还有其他几个选项:

  • 使用itertools.repeat()重复值,并使用itertools.chain.from_iterable()连接这些迭代器: chain.from_iterable(repeat(v, 3) for v in my_list)。这会产生一个迭代器,您可以在其上使用list()来再次获取列表。
  • 使用chain.from_iterable(zip(*([my_list] * 3)))创建重复值的迭代器(重复的列表转换为重复的列)。当然,这不太易读,但我们可以在此处也使用repeat()使其稍微好一些:chain.from_iterable(zip(*repeat(my_list, 3)))。同样,使用list()来产生与列表推导相同的输出。

9
有什么想法吗?即使在2021年,这仍然不可能,我没有看到任何这种语法上的歧义... - karlosss
4
@karlosss 在 PEP 448 中提到的内容被删除了,因为人们担心它会影响代码的可读性。具体请参见 variations section - Martijn Pieters
2
@nyuszika7h:如果您想就这个决定进行辩论,请将其带到Python讨论版或邮件列表中。这里的评论仅用于帮助管理问题和答案。 - Martijn Pieters
这应该是[v[i] for v in my_list for i in range(3)],对吧? - Giuliano Collacchioni
@GiulianoCollacchioni:不行,因为这将与my_list * 3相同,即重复列表系列,而不是在重复下一个值之前重复每个值,因此得到的是[1, 2, 1, 2, 1, 2],而不是预期的[1, 1, 1, 2, 2, 2]。您也可以交换for循环顺序以获得其中之一(例如[v for _ in range(3) for v in my_list])。 - Martijn Pieters
@karlosss:这是不可能的,因为它是有歧义的。*iterable 表示 "可迭代元素的序列",但这个序列应该是什么类型呢?所以你必须使用 [*iterable]*iterable,{*iterable_of_hashables}{**mapping} - Andrei Korshikov

17

采纳的答案是正确的,但我进行了一些效率测试,所以分享给过路人。

总结:使用chain.from_iterable可以比列表推导式提高约两倍的速度。如果你不介意导入numpy,则使用np.repeat可以提高约六倍的速度,但是如果最终转回list不要使用np.repeat

In [1]: from itertools import chain
   ...: import numpy as np
   ...: 
   ...: def nested_list_comprehension(seq, repeats):
   ...:     return [v for v in seq for _ in range(repeats)]
   ...: 
   ...: def chain_from_iterable_tuple(seq, repeats):
   ...:     return list(chain.from_iterable((v,) * repeats for v in seq))
   ...: 
   ...: def chain_from_iterable_list(seq, repeats):
   ...:     return list(chain.from_iterable([v] * repeats for v in seq))
   ...: 
   ...: def numpy_repeat_list(seq, repeats):
   ...:     return list(np.repeat(seq, repeats))
   ...: 
   ...: def numpy_repeat(seq, repeats):
   ...:     return np.repeat(seq, repeats)

In [2]: seq = list(range(1000))
   ...: repeats = 100

In [3]: assert (
   ...:     nested_list_comprehension(seq, repeats)
   ...:     == chain_from_iterable_tuple(seq, repeats)
   ...:     == chain_from_iterable_list(seq, repeats)
   ...:     == numpy_repeat_list(seq, repeats)
   ...: )

In [4]: %timeit nested_list_comprehension(seq, repeats)
   ...: %timeit chain_from_iterable_tuple(seq, repeats)
   ...: %timeit chain_from_iterable_list(seq, repeats)
   ...: %timeit numpy_repeat_list(seq, repeats)
   ...: %timeit numpy_repeat(seq, repeats)
1.53 ms ± 2.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
814 µs ± 3.79 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
842 µs ± 2.02 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
3.65 ms ± 22.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
268 µs ± 1.44 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接