2

I was much surprised by this

>>> def f(arg=['local variable']):
...     arg.append('!')
...     print arg
...
>>> f()
['local variable', '!']
>>> f()
['local variable', '!', '!']
>>> f()
['local variable', '!', '!', '!']

I'm sure this is by design, but WTF?!? And how should I flush the local namespace, so I'm not surprised in the future?

[EDIT]:

Thank you for the explanation about why this happen. But on a practical level, what is the right way of dealing with it? Just deepcopy all default variables before handling them, like below?

>>> from copy import deepcopy
>>> def f(arg=['local variable']):
...     arg = deepcopy(arg)
...     arg.append('!')
...     print arg
...
>>> f()
['local variable', '!']
>>> f()
['local variable', '!']
>>> f()
['local variable', '!']
dmvianna
  • 15,088
  • 18
  • 77
  • 106
  • 1
    possible duplicate of ["Least Astonishment" in Python: The Mutable Default Argument](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) – Simeon Visser Feb 13 '15 at 01:14
  • I have added explanation for the edit as well – StenSoft Feb 13 '15 at 01:39

1 Answers1

3

The local variables are not there any more. The problem is a bit more complex.

def f(arg=['local variable'])

defines a method having a parameter with a default value. As you should know, Python does not copy objects but passes references to them. This means there is some object instance created for the default value, and if you do not specify the parameter, reference to it is passed in the parameter (ie. the instance is not copied). This is usually pretty intuitive if the object is immutable (such as strings) but a list is mutable. When you mutate it, you change the instance that is used for the default value and so all subsequent calls will see that.

You can better imagine it if you think of the function as a class instance (which it actually is because everything in Python is an object):

class _f:
    __arg = ['local variable']

    def __call__(self, arg = __arg):
        # The method body

f = _f()

Now you can see that the default parameter instance has actually the same lifetime as the function itself.

The best way to avoid this problem is to use immutable objects so instead of a list, use a tuple, and instead of a class instance, use None and construct an instance in the method:

def f(arg=('local variable',)):
    arg=list(arg)
StenSoft
  • 9,369
  • 25
  • 30
  • Yes, but it is a bit puzzling that a reference exists for a variable that is not in the global namespace between different function calls. – dmvianna Feb 13 '15 at 01:22
  • 1
    The default parameter instance is hidden inside the function object just like member variables are inside class instances. I have edited the answer to better explain this. – StenSoft Feb 13 '15 at 01:24
  • A tuple doesn't solve the problem. It just throws an exception. I used a mutable object for a reason. I'm using recursion and do need to pop() and append() stuff so that the function can consume items. – dmvianna Feb 13 '15 at 02:45
  • You can create list from tuple with `list(tuple)` or you can use `None` – StenSoft Feb 13 '15 at 10:12
  • Using `None` defeats the purpose of having a default argument. However I like the idea of `list(tuple)` or even `list(list)`, as it does create a new instance (just as `deepcopy` would). Thanks for the idea. I'll accept your answer after you include the `arg = list(arg)` in it. – dmvianna Feb 16 '15 at 00:58