Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I change the list I'm iterating from when using yield

Tags:

python

I have some reproducible code here:

def test():
    a = [0, 1, 2, 3]
    for _ in range(len(a)):
        a.append(a.pop(0)) 
        for i in range(2,4):
            print(a)
            yield(i, a)

This prints out:

[1, 2, 3, 0]
[1, 2, 3, 0]
[2, 3, 0, 1]
[2, 3, 0, 1]
[3, 0, 1, 2]
[3, 0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3]

Which is what I expected, but when I do list(test()) I get:

[(2, [0, 1, 2, 3]),
 (3, [0, 1, 2, 3]),
 (2, [0, 1, 2, 3]),
 (3, [0, 1, 2, 3]),
 (2, [0, 1, 2, 3]),
 (3, [0, 1, 2, 3]),
 (2, [0, 1, 2, 3]),
 (3, [0, 1, 2, 3])]

Why is this the case, and what can I do to work around it?

like image 788
YellowPillow Avatar asked Aug 01 '17 15:08

YellowPillow


People also ask

Can I modify list while iterating?

The general rule of thumb is that you don't modify a collection/array/list while iterating over it. Use a secondary list to store the items you want to act upon and execute that logic in a loop after your initial loop. Show activity on this post. And it should do it without any errors or funny behaviour.

What happens if you modify a list while iterating Python?

Modifying a sequence while iterating over it can cause undesired behavior due to the way the iterator is build. To avoid this problem, a simple solution is to iterate over a copy of the list. For example, you'll obtain a copy of list_1 by using the slice notation with default values list_1[:] .


2 Answers

Because you always return (i,a) now a is a reference to the list. So you constantly return a reference to the same list. This is no problem for the print statement, since it immediately prints the state of a at that moment.

You can return a copy of the list, for instance like:

def test():
    a = [0, 1, 2, 3]
    for _ in range(len(a)):
        a.append(a.pop(0)) 
        for i in range(2,4):
            print(a)
            yield(i, list(a))
like image 177
Willem Van Onsem Avatar answered Oct 12 '22 22:10

Willem Van Onsem


You're yielding the same list every time, so the caller's list simply has a bunch of references to that list, which has been updated each time they called. You need to make copies of the list when you yield:

def test():
    a = [0, 1, 2, 3]
    for _ in range(len(a)):
        a.append(a.pop(0)) 
        for i in range(2,4):
            print(a)
            yield(i, a[:])
like image 35
Barmar Avatar answered Oct 12 '22 22:10

Barmar