Reputation: 4270
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?
Upvotes: 6
Views: 71
Reputation: 7443
You end up with a list of tuples, and the second element of the tuple is the same list. You might notice that they are all equivalent to the last list the generator makes, not the first; the list is changing, but the tuples all have references to the same one.
To make this clear, try to modify one of the lists. For example, if you run l[0][1].append(4)
, you will get
[(2, [0, 1, 2, 3, 4]),
(3, [0, 1, 2, 3, 4]),
(2, [0, 1, 2, 3, 4]),
(3, [0, 1, 2, 3, 4]),
(2, [0, 1, 2, 3, 4]),
(3, [0, 1, 2, 3, 4]),
(2, [0, 1, 2, 3, 4]),
(3, [0, 1, 2, 3, 4])]
All of the lists had 4 appended, because there is only one list.
If you want to return a copy, there are several ways. You could yield (i, a[:])
(using slice notation to get a copy), yield (i, list(a))
(using the list
constructor to get a copy), or yield (i, copy.copy(a))
(using the copy
module).
Upvotes: 1
Reputation: 16224
Explicit better implicit:
import copy
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, copy.copy(a))
Upvotes: 1
Reputation: 781340
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[:])
Upvotes: 3
Reputation: 476813
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))
Upvotes: 3