Question

Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument

Consider the following two functions

def a(var=[0]):
    print (var)
    var = var + [4]

def b(var=[0]):
    print (var)
    var.append(4)

They should act the same, but more importantly, both should simply print '[0]' if called with no arguments. The act very differently and only a() will print '[0]' all the time.

>>> a()
[0]
>>> a()
[0]
>>> a()
[0]

>>> b()
[0]
>>> b()
[0, 4]
>>> b()
[0, 4, 4]

Why does a() function differently from b()?

It seems like you can't use the list API if you want the variable to be overwritten with the default in the case that no arguments are past to the function. And if you do the function will 'remember' what it the variable was previously.

The situation in my code appears in a recursive function, so just 'del'-ing the unwanted variable won't really work. Is there a way to overwrite a variable every time you call the function with no arguments?

After hours and hours of research I discovered this. It might be related to the question above and might lead to an answer. We can define a lifetime class like so:

class lifetime:   # adapted from http://naml.us/blog/tag/variable-lifetime
    def __init__(self, name):
        self.name = name
        print ('creating: ' + name)
    def __del__(self):
        print ('destroying: ' + self.name)
    def __iadd__(self, a):
        self.append(a)
    def append(self, a):
        self.name += ' and ' + a.name
        print('appending: ' + a.name + ' to ' + self.name)

and then define 2 functions:

def a(var=lifetime('a')):
    print (var)
    var += life('appendage')

def b(var=lifetime('b')):
    print (var)
    var.append(life('appendage'))

>>> a()
<__main__.lifetime object at 0x00000000031FFA90>
creating: appendage
appending: appendage to a and appendage
destroying: appendage
>>> a()
<__main__.lifetime object at 0x00000000031FFA90>
creating: appendage
appending: appendage to a and appendage and appendage
destroying: appendage
>>> b()
<__main__.lifetime object at 0x00000000031FFB38>
creating: appendage
appending: appendage to b and appendage
destroying: appendage
>>> b()
<__main__.lifetime object at 0x00000000031FFB38>
creating: appendage
appending: appendage to b and appendage and appendage
destroying: appendage

It seems like it just evaluates the default for the argument once, and then uses whatever the evaluation for that was. It never says, 'creating: a' or 'creating: b'. So maybe if it were to evaluate the default argument each time, it would lead to an answer to the actual question.

Was it helpful?

Solution

Because the two code snippets do two inherently different things. var + [4] creates a new list consisting of the contents of the list of var with 4 added, we then assign this new list to var. var.append(4) on the other hand appends the number 4 to the existing list.

Your problem is that var=[0] is only evaluated once and then reused, since your append changes the variable itself you'll see the effect on later invocations. The other code only assigns a new object to a local variable which won't have an effect later.

Usually you don't use modifiable defaults but instead write:

def foo(var=None):
    if var is None: var = [0]

If you need to remember data from earlier invocations, put the whole function into a class.

OTHER TIPS

Those defaults are, just as you said, evaluated only once, when the function is declared. That's why it's never a good idea to assign a list or hash literal as a default value, because you'll only get one instance for all function calls.

That problem is described here.

The two functions behave differently because in the first you are redefining the local variable var to be [0] + [4] (not appending to the original list initialised with [0]), whereas in the second you are actually appending to that original initialisation list to take it from [0] to [0, 4]. And as you mentioned - the defaults are evaluated only once.

It may be easier to picture what is happening if you write it like

t1 = [0]
def a(var=t1):
    print (var)
    var = var + [4]

t2 = [0]
def b(var=t2):
    print (var)
    var.append(4)

It's easy. In the first function, a(), You are reassigning the "name" var to a new list containing the contents of the old var and [4]. In the second function, b(), you are accessing the actual var object without changing what the name is referring to. See this question for an explanation as to why b() is responding the way it is: “Least Astonishment” in Python: The Mutable Default Argument

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top