1

I accidently noticed that directly updating elements in a list of lists was working.

There are lots of Q&A about updating a list of lists. Yet all the answers I inspected indicate that

  • inside the iteration python generates a copy of the list's elements
  • changing the copied element does not change the list itself
  • you need to use a list comprehension, an index into the list or something alike to change the list

But in the following example I can directly update a list.

All answers I found about the subject are fairly old, so is this later on added to python?

# COMPREHENSION ==================================
li = [
    ["spam1", "eggs1"],
    ["spam2", "eggs2"]
    ]

print("\nCOMPREHENSION ---------------")
print("INPUT", li)
li = [["new", l[1]] for l in li]
print("OUTPUT", li)

# INDEX ===========================================
li = [
    ["spam1", "eggs1"],
    ["spam2", "eggs2"]
    ]
print("\nINDEX -----------------")
print("INPUT", li)
for index, l in enumerate(li):
    li[index] = ["new", l[1]]
print("OUTPUT", li)

# IN PLACE ========================================
li = [
    ["spam1", "eggs1"],
    ["spam2", "eggs2"]
    ]
print("\nIN PLACE --------------")
print("INPUT", li, "       <<<<<<<<<<<<<")
for i in li:
    i[0] = f'new'
print("OUTPUT", li, "      <<<<<<<<<<<<<")

Result

COMPREHENSION ---------------
INPUT [['spam1', 'eggs1'], ['spam2', 'eggs2']]
OUTPUT [['new', 'eggs1'], ['new', 'eggs2']]

INDEX -----------------
INPUT [['spam1', 'eggs1'], ['spam2', 'eggs2']]
OUTPUT [['new', 'eggs1'], ['new', 'eggs2']]

IN PLACE --------------
INPUT [['spam1', 'eggs1'], ['spam2', 'eggs2']]        <<<<<<<<<<<<<
OUTPUT [['new', 'eggs1'], ['new', 'eggs2']]           <<<<<<<<<<<<<
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Mace
  • 1,355
  • 8
  • 13

2 Answers2

2

Everything actually works as expected. You are right, that inside iteration element is just copied, so changing the copied element does not change actual list. See example:

In [1]: x = [1,2,3,4,5,6,7,8]

In [2]: for a in x:
  ...:     if a ==2:
  ...:         a=3

In [3]: x
Out[3]: [1, 2, 3, 4, 5, 6, 7, 8]

What happens, when an element of list is actually a list?

In [4]: z = [[1,2],[3,4],[5,6]]

In [5]: for a in z:
  ...:     if a[0] ==3:
  ...:         a = [7,8]

In [6]: z
Out[6]: [[1, 2], [3, 4], [5, 6]]

You have a copy of an inner list assigned to a, so assigning to a another list does not change outer list z.

However what is actually copied inside iteration is not an inner list, but a reference to it. So a points to inner list itself, not its copy. You can actually modify this inner list elements. Like here:

In [8]: for a in z:
  ...:     if a[0] ==3:
  ...:         a[0] = 7

In [9]: z
Out[9]: [[1, 2], [7, 4], [5, 6]]

Or even add/remove elements from it:

In [10]: for a in z:
   ...:     if a[0] ==1:
   ...:         a.append(22)


In [10]: z
Out[10]: [[1, 2, 22], [7, 4], [5, 6]]
running.t
  • 5,329
  • 3
  • 32
  • 50
  • I already expected something like this after reading Serge's answer. Thanks for the clear explanation. – Mace Dec 15 '20 at 10:18
0

This is valid, because you replace an element in a list using its index, and this has no impact on the iteration.

What is forbidden is to add or remove elements to the list being iterated, what you never do.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • The only index I use in the 3rd example is on the inner list elements. Is that what you mean? – Mace Dec 15 '20 at 10:09