55

I am trying to create a list based on another list, with the same values repeated 3 times consecutively.

At the moment, I am using:

>>> 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]

But I would like to do it using a more Pythonic way, such as:

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

However, I cannot find a way to unpack the tuples.

Something like three_times = [ *( (value,) * 3 ) for value in my_list ] would be perfect for unpacking the tuples but this is not a correct syntax.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
DRz
  • 1,078
  • 1
  • 11
  • 29

2 Answers2

55

You can't use * iterable unpacking in a list comprehension, that syntax is only available in calls, and in Python 3, when using assignments.

If you want to use a list comprehension, just put your for loops in series; you do want to access the values from my_list directly rather than generate indices though:

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

There are several other options too:

  • Use itertools.repeat() to repeat the values, and itertools.chain.from_iterable() to concatenate those iterators back together: chain.from_iterable(repeat(v, 3) for v in my_list). That produces an iterator, you can use list() on it to get a list again.
  • Use chain.from_iterable(zip(*([my_list] * 3))) to create an iterator over the repeated values (the repeated list is transposed into repeated columns). This is not nearly as readable, of course, but we could make it slightly less bad by using repeat() here too: chain.from_iterable(zip(*repeat(my_list, 3))). Again, use list() to produce the same output as the list comprehension.
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 9
    Any ideas why? Even in 2021, this is not possible and I don't see any ambiguities in such a syntax... – karlosss Jul 23 '21 at 20:41
  • 4
    @karlosss: it was taken out of PEP 448, due to strong concerns over readability, see the [variations section](https://www.python.org/dev/peps/pep-0448/#variations). – Martijn Pieters Jul 23 '21 at 21:13
  • 2
    @nyuszika7h: if you want to debate the decision, please take it to the [Python discussion board](https://discuss.python.org/) or a mailinglist. Comments here are to help manage questions and answers only. – Martijn Pieters Apr 12 '22 at 10:05
  • That should be `[v[i] for v in my_list for i in range(3)]`, probably? – Giuliano Collacchioni Sep 29 '22 at 19:08
  • @GiulianoCollacchioni: No, because that would be the same as `my_list * 3`, the list series repeated, rather than each value repeated before the next value is repeated, so `[1, 2, 1, 2, 1, 2]` instead of the expected `[1, 1, 1, 2, 2, 2]`. You can swap the `for` loop order to get one or the other, too (so `[v for _ in range(3) for v in my_list]`. – Martijn Pieters Oct 07 '22 at 09:24
  • @karlosss: it is impossible because it **is** ambiguous. `*iterable` means "sequence of elements of iterable", but what type this sequence should be? So you have to use `[*iterable]` or `*iterable,` or `{*iterable_of_hashables}` or `{**mapping}`. – Andrei Korshikov May 02 '23 at 13:56
17

Accepted answer is correct, but I made some efficiency tests, so sharing it for passers-by.

Summary: Use chain.from_iterable for ~x2 speed improvement over list comprehension. And use np.repeat for ~x6 speed improvement if you don't mind importing numpy, but don't use np.repeat if eventually converting back to list.

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)
paime
  • 2,901
  • 1
  • 6
  • 17