9

I have the following code and variables, and I want to find what the variables a, a1, a2, b, b1, and b2 refer to after the code has been executed.

def do_something(a, b):
    a.insert(0, "z")
    b = ["z"] + b

a = ["a", "b", "c"]
a1 = a
a2 = a[:]
b = ["a", "b", "c"]
b1 = b
b2 = b[:]

do_something(a, b)

My attempted solution is as follows:

a = ["z", "a", "b", "c"]
a1 = ["a", "b", "c"]
a2 = ["a", "b", "c"]
b = ["z" "a", "b", "c"]
b1 = ["a", "b", "c"]
b2 = ["a", "b", "c"]

But the actual solution is:

a = ["z", "a", "b", "c"]
a1 = ["z", "a", "b", "c"]
a2 = ["a", "b", "c"]
b = ["a", "b", "c"]
b1 = ["a", "b", "c"]
b2 = ["a", "b", "c"]

Can anyone walk me through my mistake?

kylieCatt
  • 10,672
  • 5
  • 43
  • 51
Susie
  • 139
  • 1
  • 2
  • 5
  • At what point do you find/print out the values of all the variables? Perhaps this has something to do with scope – Totem Apr 22 '15 at 22:18
  • 2
    I think by "my attempted solution" you mean "what I'm expecting" ... is that correct? That might clear up some confusion. – Ajean Apr 22 '15 at 22:20
  • 2
    Read [this](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) may help understand why this happened – Anzel Apr 22 '15 at 22:20

1 Answers1

9

Ok, you can think of variables in Python as references. When you do:

a1 = a

Both a1 and a are references to the same object, so if you modify the object pointed by a you will see the changes in a1, because - surprise - they are the same object (and the list.insert method mutates the list in place).

But when you do:

a2 = a[:]

Then a2 is a new list instance, and when you modify a you are not modifying a2.

The result of the + operator for lists is a new list, so when you do:

b = ['z'] + b

You are assigning a new list to b instead of mutating b in-place like you would do with b.insert('z'). Now b points to a new object while b1 still points to the old value of b.

But the scopes can be even trickier: you can see the enclosing scope from a function, but if you assign a variable inside the function, it will not change the variable with the same name in the enclosing (or global, or built-in) scope, it will create a variable with this name in the local scope. This is why b was not changed - well, not quite, parameter passing in Python is an assignment operation, so b is already a local variable when there is a parameter named b - but trying to mutate a variable defined in the enclosing scope leads to a similar problem. BTW, it is bad practice to rely on variables from the enclosing scope unless they are module-level constants (traditionally they are named in ALL_UPPERCASE style and you are not supposed to mutate them). If you need the value inside the function, pass it as a parameter, and it you want to change the value of the variable in the enclosing scope, return the value and assign the returned value there:

def do_something(a, b):
    a.insert(0, "z")  # mutates a in-place
    return ["z"] + b

b = do_something(a, b)
Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
  • 2
    also worth mentioning `b` in `do_something` has been reassigned locally, so that doesn't change the global `b` value. – Anzel Apr 22 '15 at 22:23
  • 2
    The value of `b` calculated in `do_something()` is local to the function and is thrown away when that function ends. None of the global variables `b`, `b1`, or `b2` are touched by the function. – kindall Apr 22 '15 at 22:28