Inappropriately marked as a duplicate question. This has nothing to do with not knowing how to pass a variable by reference. It is asking if there is a way to mitigate the issues with keeping a mutable optional argument as a parameter without its value persisting in the function's context on subsequent calls.

So I just had this happen to me today and am now familiar with this trap that many new Python developers fall into: mutable default arguments. For brevity, here's an example of what I'm taking about:

def MyClass(object):
        ...
    def my_func(self,my_str="default",my_dict={},**kwargs):
        my_dict.update(kwargs)
        self.my_dict = my_dict

----------------------------------------
<< a = MyClass()
<< a.my_func(foo='bar')
<< a.my_dict
>> {'foo':'bar'}

<< b = MyClass()
<< b.my_func(bar='baz')
<< b.my_dict
>> {'foo': 'bar', 'bar': 'baz'}

I understand that this is intentional and understand that a good solution would be to modify my_func to this instead:

def my_func(self,my_str="default",my_dict=None,**kwargs)
    if my_dict is None: my_dict = {}
                 ...

However I'm not satisfied with this. It just rubs me the wrong way. I don't want it to be None by default. I want an empty dict by default. So I thought of doing this:

def my_func(self,my_str="default", my_dict={}, **kwargs)
    my_dict.update(kwargs)
    self.my_dict = my_dict
    my_dict = {}

But of course this won't work, you'll get the same behavior as before. But why? Isn't my_dict persistent and mutable? Or only when convenient for Python? Is there any other way to deal with this aside from simply excluding my_dict as an optional arg?

I'm really hung up on this because I find it useful to use a default argument to convey a type to the reader, which boosts code readability IMO. It's good because the reader wouldn't need to dive into the function to determine that my_dict is supposed to be a dict (inb4 suggesting "just use comments" - idealistic, but not very realistic for my purposes).

This "feature" makes little sense and is so incredibly offensive to me that I'm actually very close to believing that it's really a bug in disguise. No one will ever use this and it's almost a dealbreaker for me.

Thanks in advance.

1

There are 1 answers

6
BrenBarn On BEST ANSWER

If I understand you right, you are thinking that by assigning my_dict = {}, you are "resetting" the value so that next time you call the function, it will be empty again.

That's not how default arguments work. Rather, it works something like this:

  1. When you define your function, a "secret" object is created that holds the default values of the arguments. (You can see this secret object as func.__defaults__ on a plain function, although there are some additional complications if you are looking at a method object.)
  2. Every time you call the function, if you didn't pass a value for a given argument, Python assigns the value of that argument to the previously-stored secret object.

In other words, Python does not really store the value of my_dict across calls. It stores one default value, and retrieves it on every call (unless you pass a new one).

When you do my_dict = {}, you are just assigning a new value to the local variable my_dict, which leaves the secret object unaffected. When you do something like my_dict.update(), on the other hand, you are mutating the secret object. (If it surprises you that assigning to a variable and calling a method like update have different effects, then you should search for a great many other questions on here about assignment, variable binding, mutable vs immutable objects, and the like in Python.)