Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pythonic way to unpack an iterator inside of a list

I'm trying to figure out what is the pythonic way to unpack an iterator inside of a list.

For example:

my_iterator = zip([1, 2, 3, 4], [1, 2, 3, 4])

I have come with the following ways to unpack my iterator inside of a list:

1)

my_list = [*my_iterator]

2)

my_list = [e for e in my_iterator]

3)

my_list = list(my_iterator)

No 1) is my favorite way to do it since is less code, but I'm wondering if this is also the pythonic way. Or maybe there is another way to achieve this besides those 3 which is the pythonic way?

like image 695
kederrac Avatar asked Sep 11 '19 21:09

kederrac


People also ask

How do I unpack an iterable in Python?

There are 2 ways to unpack iterables in Python. For known length iterables - Providing the exact number of variables to unpack as the number of elements in the sequence/iterable. For arbitrary length iterables - Using star expressions (*) to unpack when you are unsure of the number of variables to pass.

How do you unpack a list element in Python?

Summary. Unpacking assigns elements of the list to multiple variables. Use the asterisk (*) in front of a variable like this *variable_name to pack the leftover elements of a list into another list.

What is iterable unpacking?

Introduction. Unpacking in Python refers to an operation that consists of assigning an iterable of values to a tuple (or list ) of variables in a single assignment statement. As a complement, the term packing can be used when we collect several values in a single variable using the iterable unpacking operator, * .

How do I unpack a tuple in a list?

Python uses the commas ( , ) to define a tuple, not parentheses. Unpacking tuples means assigning individual elements of a tuple to multiple variables. Use the * operator to assign remaining elements of an unpacking assignment into a list and assign it to a variable.


3 Answers

This might be a repeat of Fastest way to convert an iterator to a list, but your question is a bit different since you ask which is the most Pythonic. The accepted answer is list(my_iterator) over [e for e in my_iterator] because the prior runs in C under the hood. One commenter suggests [*my_iterator] is faster than list(my_iterator), so you might want to test that. My general vote is that they are all equally Pythonic, so I'd go with the faster of the two for your use case. It's also possible that the older answer is out of date.

like image 194
Matt L. Avatar answered Oct 25 '22 02:10

Matt L.


After exploring more the subject I've come with some conclusions.

There should be one-- and preferably only one --obvious way to do it

(zen of python)

Deciding which option is the "pythonic" one should take into consideration some criteria :

  • how explicit,
  • simple,
  • and readable it is.

And the obvious "pythonic" option winning in all criteria is option number 3):

list = list(my_iterator)

Here is why is "obvious" that no 3) is the pythonic one:

  • Option 3) is close to natural language making you to 'instantly' think what is the output.
  • Option 2) (using list comprehension) if you see for the first time that line of code will take you to read a little bit more and to pay a bit more attention. For example, I use list comprehension when I want to add some extra steps(calling a function with the iterated elements or having some checking using if statement), so when I see a list comprehension I check for any possible function call inside or for any if statment.
  • option 1) (unpacking using *) asterisk operator can be a bit confusing if you don't use it regularly, there are 4 cases for using the asterisk in Python:

    1. For multiplication and power operations.
    2. For repeatedly extending the list-type containers.
    3. For using the variadic arguments. (so-called “packing”)
    4. For unpacking the containers.

Another good argument is python docs themselves, I have done some statistics to check which options are chosen by the docs, for this I've chose 4 buil-in iterators and everything from the module itertools (that are used like: itertools.) to see how they are unpacked in a list:

  • map
  • range
  • filter
  • enumerate
  • itertools.

After exploring the docs I found: 0 iterators unpacked in a list using option 1) and 2) and 35 using option 3).

enter image description here

Conclusion :

The pythonic way to unpack an iterator inside of a list is: my_list = list(my_iterator)

like image 31
kederrac Avatar answered Oct 25 '22 00:10

kederrac


While the unpacking operator * is not often used for unpacking a single iterable into a list (therefore [*it] is a bit less readable than list(it)), it is handy and more Pythonic in several other cases:

1. Unpacking an iterable into a single list / tuple / set, adding other values:

mixed_list = [a, *it, b]

This is more concise and efficient than

mixed_list = [a]
mixed_list.extend(it)
mixed_list.append(b)

2. Unpacking multiple iterables + values into a list / tuple / set

mixed_list = [*it1, *it2, a, b, ... ]

This is similar to the first case.

3. Unpacking an iterable into a list, excluding elements

first, *rest = it

This extracts the first element of it into first and unpacks the rest into a list. One can even do

_, *mid, last = it

This dumps the first element of it into a don't-care variable _, saves last element into last, and unpacks the rest into a list mid.

4. Nested unpacking of multiple levels of an iterable in one statement

it = (0, range(5), 3)
a1, (*a2,), a3 = it          # Unpack the second element of it into a list a2
e1, (first, *rest), e3 = it  # Separate the first element from the rest while unpacking it[1]

This can also be used in for statements:

from itertools import groupby

s = "Axyz123Bcba345D"
for k, (first, *rest) in groupby(s, key=str.isalpha):
    ...
like image 5
GZ0 Avatar answered Oct 25 '22 00:10

GZ0