12

I have an iterator over a mutable sequence, e.g.

foo = [1, 2, 3, 4, 5]
for bar in foo:

Is there a way to write to the elements in foo by using the reference which is contained in the iterator? The naive assignment:

   bar = 42

does not work of course. Is it possible to use the "behind the curtain" reference to the sequence element which is in the iterator ?

PS: The simple solution with using an index

for i in range(len(a)):
   a[i] = 42

will not work for my case, as I can't expose the container name.

6
  • You can't expose the container name? Your for loop requires a reference, so to get back to the original container all you need is that same reference. Commented Oct 24, 2013 at 9:43
  • @MartijnPieters Imagine that this is an iterator coming out of a class where the true container resides. The iterator will not let me subscript itself. Commented Oct 24, 2013 at 9:58
  • In that case you cannot alter the container anyway. Python objects can be referenced from many different locations; bar is one such reference, the list referenced by foo is another. Which one is the 'true' reference you wanted to rebind here? What if I copied the foo list into another, should references to 42 be rebound in that list too? Commented Oct 24, 2013 at 10:00
  • The class is holding a name which references the list. This list is what I want to modify. I want to do myobject.foo[i]=42 but through the iterator which is just running over myobject.foo[]. It seems impossible to revector a list element (which is a pointer technically) explicitly by only having at hand a "pointer to" the element itself. You seem to need the reference to the start of the list always in Python, correct? Commented Oct 24, 2013 at 10:15
  • 1
    You do; because the values in the list are immutable, you need to rebind the indices in the list to point to new objects. A python list is just a sequence of references, just as variable names are, albeit accessed by index, not by name. Commented Oct 24, 2013 at 10:17

3 Answers 3

24

Use enumerate() to generate indices for you in the loop:

for i, bar in enumerate(foo):
    foo[i] = bar + 42
10

From my understanding, your use case is something like this:

class Z:
    def __init__(self):
        self.a, self.b, self.c = 1, 2, 3

    def it(self):
        for x in self.a, self.b, self.c:
            yield x

z = Z()
for x in z.it():
    if x == 1:
       x = 42 # z.a should be 42? - doesn't work!

This isn't possible in python - there's no "pointer" or "reference" data type. You can work around this by yielding a setter function instead of (or along with) the value:

class Z:
    def __init__(self):
        self.a, self.b, self.c = 1,2,3

    def it(self):
        for x in 'abc':
            yield getattr(self, x), lambda y: setattr(self, x, y)

z = Z()
for x, setter in z.it():
    if x == 1:
       setter(42) # works!
1
  • The use case that I try to solve is "read element and modify(overwrite) it if it meets a criteria", so I would need an iterator which yields me a read and a write function. This could work... thanks, nice one! :) Commented Oct 24, 2013 at 10:31
5

You could use a list comprehension:

foo[:] = [expression for bar in foo]

or, if the assignment is too complicated for an expression, you could use a function, func:

foo[:] = [func(bar) for bar in foo]

If you only have an iterator, then there is no way to reassign values to the underlying container for there is no guarantee that there even is an underlying container:

def generator():
    for i in range(10):
        yield i

for bar in generator():
    # Nothing you put here can change what's going on in generator

Not the answer you're looking for? Browse other questions tagged or ask your own question.