[Tutor] References in loops

Danny Yoo dyoo at hkn.eecs.berkeley.edu
Fri Feb 11 23:32:49 CET 2005



On Fri, 11 Feb 2005, Matt Dimmic wrote:

> In Python, one bug that often bites me is this:
>
> (example A)
> aList = [1,2,3]
> for i in aList:
>     i += 1
> print aList
> --> [1,2,3]
>
> This goes against my intuition, which is that aList == [2,3,4], probably
> because so much in Python is passed by reference and not by value.


Hi Matt,


Yes.  If we "unroll" the loop, we can better see what's going on:

###
aList = [1, 2, 3]
i = aList[0]
i += 1
i = aList[1]
i += 1
i = aList[2]
i += 1
print aList
###

This has the same meaning as Example A, and it should be clearer why the
assignment to 'i' has no affect.  'i' is just a regular local variable,
just like any other local variable.

Assignment is not the same thing as value mutation; it's actually the same
issue that we talked about earlier with the default argument thread.
*grin*



When I see something like:

###
x = 42
y = x
y = y + 1
###


My visual model of this is that variable names are "arrows" to values:

--------------------------------------------------

     x -----------> 42            ##  x = 42

----------------------------------------------------

     x -----------> 42            ## y = x
                    ^
                   /
     y -----------/

----------------------------------------------------

     x -----------> 42            ## y = y + 1

     y -----------> 43

----------------------------------------------------



That being said, we can do something here with a little indirection:

###
>>> def box(x):
...     """Put a mutable wrapper around the value x."""
...     return [x]
...
>>> def unbox(boxedValue):
...     """Grab the value in the box."""
...     return boxedValue[0]
...
>>> def incr(boxedValue):
...     """Increment the value in the box."""
...     boxedValue[0] += 1
...
>>>
>>>
>>> aList = [box(1), box(2), box(3)]
>>> map(unbox, aList)
[1, 2, 3]
>>> for b in aList:
...     incr(b)
...
>>> map(unbox, aList)
[2, 3, 4]
###

This might be overkill, though.  *grin*

But this shows that, if we really needed to, we could "box" everything in
some kind of mutable container.  I don't recommend this for normal Python
programming, though.



> Of course I can always use range() or enumerate():
>
> (example B)
> aList = [1,2,3]
> for i in range(len(aList)):
>     aList[i] += 1
> print aList
> --> [4,5,6]
>
> But example A seems more elegant, if only it did what I wanted it to do.
> :) So pardon my ignorance if the answer is obvious, but what is the
> simplest way in Python to get a reference to an element in a list? Is it
> really Example B?

A variation of Example B, with enumerate(), is probably the most
straightforward way to do in-place mutation on the list in Python:

###
aList = [1, 2, 3]
for i, x in enumerate(aList):
    aList[i] = x + 1
print aList
###

There are languages that magically allow us to do mutation on a list
without making it look like list mutation.  Perl is one of those languages
that adds magic syntactic sugar for doing in-place list stuff.  But Python
has no special mechanisms for doing this.


The main issue that I think people are running against is that, in Python,
numeric values are "immutable" --- they can't be changed.  When we do
things like addition:

###
>>> x = 7
>>> x = x + 1
###

we are not changing the value of '7': we're just "re-aiming" or directing
'x' to the new value '8'.


If you have more questions, please feel free to ask!



More information about the Tutor mailing list