tags:

views:

741

answers:

6

I'm new to Python and am trying to understand its approach to variable scope. In this example, why is f() able to alter the value of x, as perceived within main(), but not the value of n?

def f(n, x):
    n = 2
    x.append(4)
    print 'In f():', n, x

def main():
    n = 1
    x = [0,1,2,3]
    print 'Before:', n, x
    f(n, x)
    print 'After: ', n, x

main()

Output:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]
+8  A: 

f doesn't actually alter the value of x (which is always the same reference to an instance of a list). Rather, it alters the contents of this list.

In both cases, a copy is passed to the function. But since x is a reference to a list instance, only the reference is copied, not the contents of the list.

In case you are familiar with C, the following approaches the semantics of the Python code:

void f(int n, int* x) {
    n = 42;
    x[0] = 2;
}

Here, n is an int and x is an int* but both are passed as a copy to the function. Nontheless, the memory pointed to by x is the same at the sides of the caller and the callee.

Konrad Rudolph
"copy" is misleading. Python doesn't have variables like C. All names in Python are references. You can't modify name, you just can bind it to another object, that's all. It only makes sense to talk about mutable and immutable *object* in Python not they are names.
J.F. Sebastian
@J.F. Sebastian: Your statement is misleading at best. It is not useful to think of numbers as being references.
dysfunctor
@dysfunctor: numbers are references to immutable objects. If you'd rather think of them some other way, you have a bunch of odd special cases to explain. If you think of them as immutable, there are no special cases.
S.Lott
@S.Lott: Regardless of what's going on under the hood, Guido van Rossum put a lot of effort into designing Python so that the programmer can thing of numbers as being just ... numbers.
dysfunctor
@J.F., the reference is copied.
Aaron Gallagher
@Aaron: It might or might not be so. Underlying implementation can construct a new object that represents a name in Python and then bind it to corresponding object. C is not the only implementation language. You can implement Python in pure Python.
J.F. Sebastian
+1  A: 

It´s because a list is a mutable object. You´re not setting x to the value of [0,1,2,3], you´re defining a label to the object [0,1,2,3].

You should declare your function f() like this:

def f(n, x=None):
    if x is None:
        x = []
    ...
Luiz Damim
+3  A: 

n is an int (immutable), and a copy is passed to the function, so in the function you are changing the copy.

X is a list (mutable), and a copy of the pointer is passed o the function so x.append(4) changes the contents of the list. However, you you said x = [0,1,2,3,4] in your function, you would not change the contents of x in main().

jcoon
Watch the "copy of the pointer" phrasing. Both places get references to the objects. n is a reference to an immutable object; x is a reference to a mutable object.
S.Lott
+4  A: 

I will rename variables to reduce confusion. n -> nf or nmain. x -> xf or xmain:

def f(nf, xf):
    nf = 2
    xf.append(4)
    print 'In f():', nf, xf

def main():
    nmain = 1
    xmain = [0,1,2,3]
    print 'Before:', nmain, xmain
    f(nmain, xmain)
    print 'After: ', nmain, xmain

main()

When you call the function f, the Python runtime makes a copy of xmain and assigns it to xf, and similarly assigns a copy of nmain to nf.

In the case of n, the value that is copied is 1.

In the case of x the value that is copied is not the literal list [0, 1, 2, 3]. It is a reference to that list. xf and xmain are pointing at the same list, so when you modify xf you are also modifying xmain.

If, however, you were to write something like:

    xf = ["foo", "bar"]
    xf.append(4)

you would find that xmain has not changed. This is because, in the line xf = ["foo", "bar"] you have change xf to point to a new list. Any changes you make to this new list will have no effects on the list that xmain still points to.

Hope that helps. :-)

dysfunctor
+9  A: 

Some answers contain a word "copy" in a context of a function call. I find it confusing.

Python doesn't copy objects you pass during a function call ever.

Function parameters are names. When you call a function Python binds these parameters to whatever objects you pass (via names in a caller scope).

Objects can be mutable (like lists) or immutable (like integers, strings in Python). Mutable object you can change. You can't change a name, you just can bind it to another object.

Your example is not about scopes or namespaces, it is about naming and binding and mutability of an object in Python.

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print 'In f():', n, x
    x = []   # put `x` label on `[]` ballon
    # the above has no effect on the original list

Here's nice pictures on the difference between variables in other languages and names in Python.

J.F. Sebastian
+1 for great referencing links on top of the nice explanation
Cawas
+2  A: 

You've got a number of answers already, and I broadly agree with J.F. Sebastian, but you might find this useful as a shortcut:

Any time you see varname =, you're creating a new variable (or name) within the function's scope. Whatever value varname had before is lost within this scope.

Any time you see varname.foo() you're calling a method on varname. The method may alter varname (e.g. list.append). varname (or, rather, the object that varname names) may exist in more than one scope, and since it's the same object, any changes will be visible in all scopes.

[note that the global keyword creates an exception to the first case]

John Fouhy