Python Forum

Full Version: question about list comprehension
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I understand most of this:

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
Output:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
Equivalent to:
>>>

>>> combs = []
>>> for x in [1,2,3]:
...     for y in [3,1,4]:
...         if x != y:
...             combs.append((x, y))
...
>>> combs
Output:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
What I don't understand is how, in the list comprehension, Python knows to make (x, y) into a list of tuples.

Please use small words. I'm a beginner!

Wait, I think I figured it out. It's already a tuple, and it's in a list. So a new tuple is made every time the conditions of the loops are met?
(Aug-22-2019, 03:19 AM)Exsul Wrote: [ -> ]Wait, I think I figured it out. It's already a tuple, and it's in a list. So a new tuple is made every time the conditions of the loops are met?
Yes, you are right. If you replace (x, y) with [x, y], you get a list of lists.
Comprehension in Python is very simple and powerful feature. However, at the beginning it may seem like magic. One way of becoming wizard is to say in spoken language what comprehension does.

Simple example:

'give me number for every number in list which is even':

[num for num in [1, 2, 3, 4, 5] if not num % 2]
To take it into pieces:

num - give me a number
for num in [1, 2, 3, 4, 5] - for every number in list
if not num % 2 - which is even
Quote:What I don't understand is how, in the list comprehension, Python knows to make (x, y) into a list of tuples.

The tuples are made inside the list comprehension explicit.
The container is around the tuples is the list.

The creation of tuples happens often implicit.
def foo():
    return 42, 1337
If you call this function, it will return (42, 1337).
You can write it more explicit:
def foo():
    return (42, 1337)
A for-loop and the assignment operator (=) are doing assignment.
If the returned result is an iterable (tuple, list, something else), you can do argument unpacking:
first, second = foo()
This is useful for enumeration for example.
colors = ['green', 'blue', 'red']
for iterable enumerate(colors):
    print(iterable)
This will return (0, 'green') (1, 'blue') (2, 'red').

Now with argument unpacking on the left side:
for index, color enumerate(colors):
    print(index, color)
You have to pay attention, you need on the left side exactly the same amout of variables, as each iteration returns elements.
To less or to much variables will lead into a ValueError.

Since Python 3.5 this was optimized, we can now consume on the left side all elements, which are too much:

first, *middle, last = range(100)  # <- will iterate over range,
                                   # because on the left side argument unpacking happens
                                   # first get one element
                                   # *middle the rest - the last element
                                   # last get's the last element

first, *last = range(100)
*start, last = range(100)
Another helpful trick is nested argument unpacking.
For example you have some iterable, which yields 2 elements for each iteration.
Then you want to count the index of it with enumerate.

def gen_2():
    """Generator to produce expected data"""
    for i in range(5):
        yield (i, i*2)

generator = gen_2()
for n, (element1, element2) in enumerate(generator):
    print(n, element1, element2)
More explicit:
generator = gen_2()
for (n, (element1, element2)) in enumerate(generator):
    print(n, element1, element2)
This works also with the assignment operator:
a, (b, c) = [1, [2, 3]]