12

I know you should not add/remove items while iterating over a list. But can I modify an item in a list I'm iterating over if I do not change the list length?

class Car(object):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return type(self).__name__ + "_" + self.name

my_cars = [Car("Ferrari"), Car("Mercedes"), Car("BMW")]
print(my_cars)  # [Car_Ferrari, Car_Mercedes, Car_BMW]
for car in my_cars:
    car.name = "Moskvich"
print(my_cars)  # [Car_Moskvich, Car_Moskvich, Car_Moskvich]

Or should I iterate over the list indices instead? Like that:

for car_id in range(len(my_cars)):
    my_cars[car_id].name = "Moskvich"

The question is: are the both ways above allowed or only the second one is error-free?

If the answer is yes, will the following snippet be valid?

lovely_numbers = [[41, 32, 17], [26, 55]]
for numbers_pair in lovely_numbers:
    numbers_pair.pop()
print(lovely_numbers)  # [[41, 32], [26]]

UPD. I'd like to see the python documentation where it says "these operations are allowed" rather than someone's assumptions.

martineau
  • 119,623
  • 25
  • 170
  • 301
dizcza
  • 630
  • 1
  • 7
  • 19
  • Yes, as long as you don't change the length you wont run into problems. For the last bit you will run into an IndexError if your list is empty before pop. – syntaxError Jul 01 '17 at 19:09
  • @dizcza The documentation would also not mention that mutating the size is allowed or not. It is. All these operations are allowed, otherwise you'd get a syntax error. It's a matter of style and what you should _not_ do as a good programmer. – cs95 Jul 01 '17 at 19:10
  • 1
    You already have two answers that say that modifying a list itself is different than modifying elements within the list. This is correct, but perhaps confusing to some people. It might help to remember that lists only contain references (i.e. pointers). For further reading, I highly recommend [this article](https://nedbatchelder.com/text/names.html) by Ned Batchelder. – John Y Jul 01 '17 at 19:36

4 Answers4

27

You are not modifying the list, so to speak. You are simply modifying the elements in the list. I don't believe this is a problem.

To answer your second question, both ways are indeed allowed (as you know, since you ran the code), but it would depend on the situation. Are the contents mutable or immutable?

For example, if you want to add one to every element in a list of integers, this would not work:

>>> x = [1, 2, 3, 4, 5]
>>> for i in x:
...     i += 1
... 
>>> x
[1, 2, 3, 4, 5] 

Indeed, ints are immutable objects. Instead, you'd need to iterate over the indices and change the element at each index, like this:

>>> for i in range(len(x)):
...     x[i] += 1
...
>>> x
[2, 3, 4, 5, 6]

If your items are mutable, then the first method (of directly iterating over the elements rather than the indices) is more efficient without a doubt, because the extra step of indexing is an overhead that can be avoided since those elements are mutable.

cs95
  • 379,657
  • 97
  • 704
  • 746
  • 1
    Python does list iteration by index, so yes, deleting items (except from the end, using a negative slice) during iteration is potentially dangerous behavior: modifying them in place though is entirely safe. – Alex Huszagh Jul 01 '17 at 19:14
  • Yeah, sounds reasonable. I just thought what will happen if inside the for-loop I begin creating many additional fields to an object (or any other operations that will consume too much memory and thus needed to be reallocated) before I finish with this object and go to the next one. – dizcza Jul 01 '17 at 19:18
  • @dizcza In python, every single bit of memory is allocated from the heap. There is no guarantee that adjacent elements inside a list are anywhere near each other in actual memory. This is definitely not a cause for concern. :) – cs95 Jul 01 '17 at 19:19
  • Can I add or remove items to the list while the for loop is running. Is that allowed? If so then will python use the new list or original list in the for loop – variable Oct 20 '19 at 04:45
  • @variable It isn't a good idea. I would just initialise a new list outside the loop and add items to it accordingly. – cs95 Oct 20 '19 at 04:47
  • I am trying to understand how for loop works. For i in list1. Does it hold a copy of the list1 when the for loop starts? If value of list1 is changed (add/delete/modify) then will it affect the next iteration or not. Thanks for your help – variable Oct 20 '19 at 04:51
  • @variable There's already a lot of discussion on the topic, so I don't want to rehash explanations. You can take a look [here](https://stackoverflow.com/questions/43206541/how-does-python-manage-a-for-loop-internally). It iterates over the list's contents directly unless you pass a copy to the loop. – cs95 Oct 20 '19 at 04:57
6

I know you should not add/remove items while iterating over a list. But can I modify an item in a list I'm iterating over if I do not change the list length?

You're not modifying the list in any way at all. What you are modifying is the elements in the list; That is perfectly fine. As long as you don't directly change the actual list, you're fine.

There's no need to iterate over the indices. In fact, that's unidiomatic. Unless you are actually trying to change the list itself, simply iterate over the list by value.

If the answer is yes, will the following snippet be valid?

lovely_numbers = [[41, 32, 17], [26, 55]]
for numbers_pair in lovely_numbers:
    numbers_pair.pop()
print(lovely_numbers)  # [[41, 32], [26]]

Absolutely. For the exact same reasons as I said above. Your not modifying lovely_numbers itself. Rather, you're only modifying the elements in lovely_numbers.

Christian Dean
  • 22,138
  • 7
  • 54
  • 87
0

Examples where the list is modified and not during while iterating over the elements of the list

list_modified_during_iteration.py

a = [1,2]
i = 0
for item in a:
      if i<5:
          print 'append'
          a.append(i+2)
      print a
      i += 1

list_not_modified_during_iteration.py (Changed item to i)

a = [1,2]
i = 0
for i in range(len(a)):
    if i<5:
        print 'append'
        a.append(i+2)
    print a
    i += 1
vineeshvs
  • 479
  • 7
  • 32
-1

Of course, you can. The first way is normal, but in some cases you can also use list comprehensions or map().

Oleksii Filonenko
  • 1,551
  • 1
  • 17
  • 27
  • 3
    You have given an answer without saying why, and are throwing around terms and concepts without describing how they would be useful to OP in any way. – cs95 Jul 01 '17 at 19:16