[Tutor] Python oddity

Luke Paireepinart rabidpoobear at gmail.com
Thu Feb 28 02:52:58 CET 2008


Keith Suda-Cederquist wrote:
> Hi,
>
> I'm using iPython and I've run into an occasional problem that I don't 
> understand.  Here is what I'm seeing:
>
> >>aa=range(0,10)
> >>bb=aa
> >>print aa
> [0,1,2,3,4,5,6,7,8,9]
> >>print bb
> [0,1,2,3,4,5,6,7,8,9]
> >> # okay, everything allright at this point
> >>bb[5]=0  #change bb
> >>print aa
> [0,1,2,3,4,0,6,7,8,9]  #aa has changed!!!
> >>print bb
> [0,1,2,3,4,0,6,7,8,9]
>
> So the problem is that when I change bb, aa also changes even though I 
> don't want it to.  Is this supposed to happen?  If it is can someone 
> explain to me why this is a good thing?  and finally, can someone give 
> me some advice on how to avoid or work-around this problem.
In Python, variables are references to objects, they are not containers.
Doing any sort of variable assignment results in you directing your new 
variable at the same object.
If you do something like:
a = 5
b = a
b = 10
print a

You will get 5 for output; this is because an integer is not mutable.
Mutability is determined based upon whether the object can be changed 
directly, or whether a new object must be constructed in order for the 
value to change.
Strings and integers are immutable in Python.  Saying
a = "hello"
a += "hi"
will not just tack on "hi" to the end of the string.  Instead, it will 
allocate a new string, with 7 characters of room, place "hello" at the 
beginning, and copy "hi" into the last 2 characters.  Then it will point 
the variable 'a' at this new string.
This is one reason why you shouldn't write programs like:
a = ""
for x in range(2000):
    a += '1'
By the time you get to the end, you will be allocating new strings of 
1997, 1998, and 1999 characters each time in order to add this one variable.
It's much faster to do:
a = []
for x in range(2000):
    a.append('1')
a = ''.join(a)

The reason this is faster is that Python lists are mutable.  So the 
.append() method doesn't have to create a new copy of your object to 
place it into the list.  You can simply add it to the end without having 
to re-allocate memory.

The mutability of certain objects unfortunately causes confusion 
early-on for beginners, but it's much more efficient that way.

If you absolutely have to copy a list, you can use copy.copy() or 
copy.deepcopy() depending on your needs.  I would suggest that you 
re-evaluate your code if you do need a copy, because a majority of the 
time you won't.

One more snag to watch out for that involves mutability is in default 
values.
Something like this:
def foo(bar=[]):
    bar.append("Hi!")
    print bar
Upon cursory examination, you might deduce that 'bar' will have a value 
of [] for every function call, if you don't specify a value to override 
the default.
However, Python only evaluates default arguments on the first occurrence 
of them that it finds, so what really happens is that you receive one 
copy of bar for the entirety of your program.  Thus, when you call 
append() on 'bar', it's actually manipulating the single copy that you 
have.  So each time you call the function, you'll have another "Hi!" in bar.
The usual method to avoid this is as follows:
def foo(bar=None):
    if bar:
        print bar
    else:
        bar = "Hi!"
        print bar

In other words, you make your default argument an immutable type 
(None).  Then, when you modify it inside the function, it doesn't affect 
its original None value.  So in this case, the variable 'bar' will 
always refer to the value 'None' whenever you don't pass any values to foo.

Hope that helps,
-Luke



More information about the Tutor mailing list