The Python Oracle

Why can a function modify some arguments as perceived by the caller, but not others?

--------------------------------------------------
Rise to the top 3% as a developer or hire one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Magic Ocean Looping

--

Chapters
00:00 Why Can A Function Modify Some Arguments As Perceived By The Caller, But Not Others?
00:30 Answer 1 Score 20
01:17 Answer 2 Score 10
02:21 Accepted Answer Score 305
03:22 Answer 4 Score 28
04:01 Thank you

--

Full question
https://stackoverflow.com/questions/5751...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python

#avk47



ACCEPTED ANSWER

Score 305


Some answers contain the word "copy" in the 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 and strings in Python). A 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
    # x = [] has no effect on the original list that is passed into the function

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




ANSWER 2

Score 28


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 name binding within the function's scope. Whatever value varname was bound to 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]




ANSWER 3

Score 20


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 of a reference is passed to the function. Inside the function,

  • n gets assigned a new value. Only the reference inside the function is modified, not the one outside it.
  • x does not get assigned a new value: neither the reference inside nor outside the function are modified. Instead, x’s value is modified.

Since both the x inside the function and outside it refer to the same value, both see the modification. By contrast, the n inside the function and outside it refer to different values after n was reassigned inside the function.




ANSWER 4

Score 10


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. :-)