Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this python generator returning the same value everytime?

I have this generator that yields lists:

def gen():
    state = [None]

    for i in range(5):
        state[0] = i
        yield state

And here's the output, when I call it:

>>> list(gen())
[[4], [4], [4], [4], [4]]

Why are all the elements [4]? Shouldn't it be [[0], [1], [2], [3], [4]]?

like image 225
piggs_boson Avatar asked Jul 28 '15 21:07

piggs_boson


People also ask

Why does a generator return in Python?

Python generators are a simple way of creating iterators. All the work we mentioned above are automatically handled by generators in Python. Simply speaking, a generator is a function that returns an object (iterator) which we can iterate over (one value at a time).

Can you reset a Python generator?

To reset a generator object in Python, we can use the itertools. tee method. to call itertools. tee with generator y to get a 2nd version of the generator.

Are generators lazy in Python?

Key takeaways: motivation and uses behind generators Generators are memory efficient since they only require memory for the one value they yield. Generators are lazy: they only yield values when explicitly asked. You can feed the output of a generator to the input of another generator to form data pipelines.

What is the value in using a generator in Python?

Python Generator functions allow you to declare a function that behaves likes an iterator, allowing programmers to make an iterator in a fast, easy, and clean way. An iterator is an object that can be iterated or looped upon.


2 Answers

You are reusing the same list object. Your generator returns the one object over and over again, manipulating it as it goes, but any other references to it see those same changes:

>>> r = list(gen())
>>> r
[[4], [4], [4], [4], [4]]
>>> r[0] is r[1]
True
>>> r[0][0] = 42
>>> r
[[42], [42], [42], [42], [42]]

Yield a copy of the list or create a new fresh list object instead of manipulating one.

def gen_copy():
    state = [None]

    for i in range(5):
        state[0] = i
        yield state.copy()  # <- copy

def gen_new():
    for i in range(5):
        state = [i]  # <- new list object every iteration
        yield state
like image 151
Martijn Pieters Avatar answered Sep 20 '22 16:09

Martijn Pieters


You are yielding the same list/object so you always see the last values added to the list. You should yield a copy:

yield state.copy()

Or create the list inside the first loop:

for i in range(5):
    state = [i]

It would be as easy to create a new list/object each time:

def gen():
    for i in range(5):
        state = [None]
        state[0] = i
        yield state
like image 32
Padraic Cunningham Avatar answered Sep 18 '22 16:09

Padraic Cunningham