[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