2

I am confused by the following example:

def append(elem, to=None):
if to is None:
    to = []
to.append(elem)
return to

append(5)
# Out: [5]    
append(3)   # A new list is created
# Out: [3]

The point here is supposed to be that you are resetting "to" to an empty list at the beginning of every function call to avoid the following:

append(5)
# Out: [5]    
append(3)   # A new list is created
# Out: [5, 3]

But how can you check that "to" is None to set it to []? It seems to me that you are either pulling in the "to" defined in the definition, or you are pulling in the "to" modified by the last call. How does this work?

Graham
  • 7,431
  • 18
  • 59
  • 84
Camsbury
  • 121
  • 3
  • 8
  • In the example you gave where ```to``` is assigned a value in the function, ```to``` only *exists* *inside* the function (its scope is only the function) - once the function returns, ```to```'s scope vanishes so it vanishes - it is created again during the next call. https://docs.python.org/3/reference/executionmodel.html#resolution-of-names, https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces – wwii Jul 26 '16 at 03:10
  • I don't know enough about this topic to know whether this should be reopened, but if it does reopen, please do remove the meta commentary. We often discourage these addendums altogether since they are rarely updatd/removed if they no longer apply. – halfer Jul 28 '16 at 20:14

2 Answers2

2

When you define a function with a default argument, the function uses that default values for that argument if it is not supplied. So in the case of append(5), to is not specified, so the function assumes the value of to to be None - it's effectively the same as calling append(5, None).

So now, the function checks if to is None, which it is, so to gets reassigned to an empty list. 5 gets appended to the list, and the list is returned.

When you make the second call append(3), it is again as if you called append(3, None). Again, the if to is None evaluates to True, and to is reassigned to an empty list. Then, 3 is appended to that empty list, and the list is returned.

Since the default argument (in this case None) is immutable, the operations on to do not persist the end of the function call. The function has its own chunk of memory which gets cleared when the function returns.

However, if the default argument was mutable (like for instance []), that value is created when the function is defined (i.e. when python sees def func(arg1, arg2=[]), it creates an empty list in memory and uses that list every time this function is called). Thus, any changes made to that list will persist the end of the function, since that mutable default argument was created when the function was defined (before the function was ever called).

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
  • Wow. So I knew most of this already, but I'm understanding that just because the default value's _type_ is immutable, the value of the list doesn't persist. So the value of "to" always does reset to None here, and the list is recreated. Thanks! – Camsbury Jul 26 '16 at 12:01
1

I'll be quoting Common Gotchas — The Hitchhiker's Guide to Python here:

For example like:

def append_to(element, to=[]):
    to.append(element)
    return to

A new list is created once when the function is defined, and the same list is used in each successive call.

Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

Also if you don't like to use none ant consider it an antipattern, you can use more fancier kind of this:

def append_to(element, to=list):
    if callable(to):
        to = to()
    to.append(element)
    return to

I don't know if it's better though

valignatev
  • 6,020
  • 8
  • 37
  • 61