Line Break in a comprehended list

table  = [['a1', 'a2', 'a3'], ['b1', 'b2', 'b3']]
for row in table:
    for cell in row:
        print(cell, '', end='')
    print()

This creates:
a1 a2 a3
b1 b2 b3

But when I try to generate a list and repeat said list 4 times, I can’t get it to line break after each iteration of the for loop.

table = [[i for i in range(1, 6)] for j in range(4)]
print(table)

I have tried adding a “print()” at various spots and even “\n” but I keep getting errors.

There’s no difference in the lists you’ve supplied, the difference is in how you’re trying to print them.

In the first case you’re looping over the list and printing each row separately. This creates a line break.

In the second case, you’re printing the entire list in one function. Only one line break is printed at the end.

I’d probably just use another comprehension to convert each row to a string and join() them, but you might prefer to print them in an explicit loop. These use the default format for a list print, but you could further modify it to remove the commas as your first example does.

table = [[i for i in range(1, 6)] for j in range(4)]

# comprehension print
print("\n".join(str(row) for row in table))

# explicit loop print
for row in table:
    print(row)

Both produce the same output:

[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

OK let me give this a go at comprehension…because I just researched what an explicit loop was and #mindblownemoji

So the explicit iterator in this is “row”?

… and you have to create a “for” loop to run through the list generated in the variable “table”, which is the “iterable”

and by default it creates a new line so there is no need for a line break at the end of each run through the loop.

By Brad Westermann via Discussions on Python.org at 16Jun2022 18:05:

OK let me give this a go at comprehension…because I just researched
what an explicit loop was and #mindblownemoji

Ah, you’ve discovered the notion of an iterable: anything which can be
iterated over.

So the explicit iterator in this is “row”?

Not quite.

table is a list containing smaller lists. Any list is iterable.
So in this line:

for row in table:

the expression table gets your list-of-lists. That is iterable because
it is a list. The mechanics of the for-loop get an iterator from the
list. An iterator is a little mechanism which returns successive
values from its source iterable. The for-loop iterates over the
iterator. Each value from the iterator is assigned to the variable
row in turn.

Let’s make this a bit more explicit:

  • the iterable is a list, obtained from the expression at the end of
    the for-loop (table)
  • the iterator is the additional new mechanism which iterates over the
    list
  • the for-loop obtains a new iterator from the iterable in order to
    collect the list values

You can get an iterator directly from an iterable with the iter()
function:

>>> L = [1, 2, 3]
>>> type(L)
<class 'list'>
>>> it = iter(L)
>>> type(it)
<class 'list_iterator'>

The reason for this distinction is that you might have multiple
iterators for some iterable thing:

>>> for i in L:
...   for j in L:
...     print(i, j)
...
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3

The outer for-loop gets an iterator for itself, and then each run of the
inner for-loop gets its own iterator, so that it can iterate over the
list elements independently to make the combinations above.

You can compare this with having just one iterator for both loops:

>>> it1 = iter(L)
>>> for i in it1:
...   for j in it1:
...     print(i, j)
...
1 2
1 3

Here, both loops “consume” the iterator, which becomes empty almost
immediately.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

When the for loop is handed the list, it creates an iterator to pull the elements from it. The iterator is hidden and not seen, but as it is consumed, it supplies an element at a time, each of which is assigned to “row” in turn.

Yes table (and all lists) are iterables.

It gets “consumed” here because the iterator is the temporary thing pulled from the iterable (a list in this case) and in this case the variable is the actual iterator instead of an full iterable so the inner loop has nothing left to pull from, correct? I’m just not sure why it gave double ones.

The outer loop gets just the first item from it1: 1, the inner loop then iterates the rest from it1: 2, 3. The inner loop iterates it1 till the end so the outer loop gets no more items from it1.

Summary:

  • outer loop is iterated once with i = 1
  • inner loop is iterated twice with j = 2 and j = 3
1 Like

By Brad Westermann via Discussions on Python.org at 17Jun2022 12:52:

It gets “consumed” here because the iterator is the temporary thing pulled from the iterable (a list in this case)

Yes. A for-loop effectively calls iter(thing) if asked to loop over
thing. The “iterator protocol” defines two actions:

  • “next”, which returns the next iter or raises StopIteration
  • “iter”, which returns itself - this way things expecting an iterable
    (a thing you can call iter() on) still work if handed an iterator

and in this case the variable is the actual iterator

Yes. The particular one you get from a list is like a little generator
function:

def list_iter(L):
    for i in range(len(L)):
        yield L[i]

I mention this because in fact the “list iterator” you get from a list
is index based, so if you modify the original list while you’re using it
you will get odd results. Don’t do that :slight_smile:

instead of an full iterable so the inner loop has nothing left to pull
from, correct?

Yes. It’s a thing which makes one pass over the list. Both lists use the
same iterator, so the outer loop gets the first item, then the innner
loop gets the remaining items, then the iterator is empty when the first
loop continues.

I’m just not sure why it gave double ones.

The first item went into i, which gets used in the print() calls in
the inner loop with j, which gets the remaining 2 and 3 in turn.
Laid out:

it = iter(L)    # L == [1, 2, 3]
i = next(it)    # gets 1
j = next(it)    # gets 2
print(i, j)     # prints 1 2
j = next(it)    # gets 3
print(i, j)     # prints 1 3

then the inner loop ends when next(it) raises StopIteration, and the
outer loop ends for the same reason when it calls next(it).

You’ll find the term “consume” used a lot; a for-loop is a consumer of
an iterator. The corresponding term is “produce”: the iterator is a
producer of values. You’ll find the same terms used with queues and
other “streams” of objects.

Cheers,
Cameron Simpson cs@cskk.id.au

Good example, Cameron. @Mitzleplick, we can deepen Cameron’s example by adding the essential consume action plus clarity on how the list[] and the iter() are different objects:

def list_iter(iterL):
    for i in range(len(iterL)):
        print(iterL)
        yield iterL[i]
        del iterL[i]
Note... (click to unfold)
        del is only here as pseudocode because...
         ...deleting a list element during iteration shifts the indexing...
         ...which often makes a mess of some sort.
L = [1, 2, 3]
list_iter(L)

As the Notes... indicate, the code above is not meant to actually be run.

In order to delete list elements without shifting the index...

We’d have to delete elements from the end. In which case, the steps are:

  • reverse the list.
  • iterate through it backwards.
  • …and use one of these:
     1.    iterL.pop()
     2.    yield iterL
            del iterL[i]