[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