-3

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.

Dan Chrostowski
  • 337
  • 2
  • 8
  • "No one will ever use this" - Wrong. I have used it. It is similar to class attributes vs instance attributes. – TigerhawkT3 Dec 22 '16 at 02:44
  • Hyperbole. I'm sure most Python devs, especially those coming from a language where this isn't a feature wouldn't think to use it. – Dan Chrostowski Dec 22 '16 at 02:45
  • `my_dict` is in fact mutable, but you aren't changing the argument `my_dict`, you are reassigning it. – Natecat Dec 22 '16 at 02:47
  • That's understandable; most people don't accidentally invent new languages by wishing they could do various things. – TigerhawkT3 Dec 22 '16 at 02:47
  • [Feature](http://docs.python-guide.org/en/latest/writing/gotchas/#when-the-gotcha-isn-t-a-gotcha). – elethan Dec 22 '16 at 02:49
  • 3
    Casting your opinion as fact and calling anyone who disagrees a "fanboi" doesn't alter your question's validity, and probably doesn't encourage Python experts - sorry, fanbois - to donate their time to helping you. – TigerhawkT3 Dec 22 '16 at 02:49
  • Don't "just use comments", just use documentation. – BrenBarn Dec 22 '16 at 02:54
  • I like Python for the most part. I'd like to be a fanboi. I just really don't like this feature. Just wish someone could show me something I can do that can help me become a Python fanboi too. Rather than try to silence me with downvotes. – Dan Chrostowski Dec 22 '16 at 02:54
  • 1
    Downvotes aren't intended to silence anyone, and aren't directed at you personally. They indicate (as the tooltip shows) that the question lacks research effort, clarity, or utility, in the voter's opinion. – TigerhawkT3 Dec 22 '16 at 02:56
  • @TigerhawkT3 - beside me forgetting that reassigning a variable has nothing to do with mutability, what exactly isn't clear about my question? What shows that I didn't put effort in? Research? It can be inferred that I researched this before asking as I started out by saying, "I know why this happens...but". I spent a good 45 minutes carefully writing it. It's got a little bit of criticism in it at the end, yeah, but I really think the motivation is because people can't take the mild criticism. This is probably very valuable to new Python devs. Sadly it's downvoted by emotional people. – Dan Chrostowski Dec 22 '16 at 03:12
  • Again, ad hominem is not an effective way of convincing people to give you free help. – TigerhawkT3 Dec 22 '16 at 03:15
  • Can't help it. Complaining is my thing. I don't hold back on the criticism. I usually intend it to be constructive as a foundation for the discussion, but have been seeing that people react to it like it's a personal attack on them. – Dan Chrostowski Dec 22 '16 at 03:20
  • 1
    I find it odd that you find a language feature "offensive" and yet label others as "emotional," and that you claim to be providing a "constructive... foundation for discussion" by calling people "fabois." – TigerhawkT3 Dec 22 '16 at 03:25
  • Again hyperbole. I don't actually mean it's offensive. It'd be silly for me to actually be offended by software. It is offensive when compared to what's typically allowed in other languages. And what's wrong with being a fanboi? Personally I'm a Perl fanboi and a Linux fanboi. Like how you might be a Mac fanboi with your iPhone, Macbook Pro and the effigy of Steve Jobs you keep in your closet and pray to 6 times a day. It's meant to poke fun not hate. Fuck, I forgot most programmers have no sense of humor. – Dan Chrostowski Dec 22 '16 at 03:33
  • Wow. @TigerhawkT3 just lost all respect for you. This isn't a duplicate question - at least not to what you marked it as. I know how to pass a variable by reference, thank you. – Dan Chrostowski Dec 22 '16 at 03:42
  • @2rare2die No that is not correct definition of "fanboi". Apart from default argument you gotta learn the default definition of "fanboi" as well :). Here you go: http://www.urbandictionary.com/define.php?term=fanboi – Mohammad Yusuf Dec 22 '16 at 05:17

1 Answers1

4

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

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • You might wanna mention `dictobj.clear()` in your answer – Natecat Dec 22 '16 at 02:51
  • @Natecat: That might be dangerous. Are you imagining calling `my_dict.clear()` at the end to reset the value? That would be bad if a non-default value was actually passed in, since it will erase whatever's in the dict you passed as your argument. – BrenBarn Dec 22 '16 at 02:53
  • @BrenBarn - essentially defeating the feature for someone who'd expect it to be there, then? That's kinda what I want to do actually. – Dan Chrostowski Dec 22 '16 at 02:58
  • @2rare2die: Not sure what you mean. It's not a matter of just defeating the default-argument feature; it will create even worse side effects. I mean that if you do `my_dict.clear()` and someone calls `my_func(my_dict=blah)`, where `blah` is some dict they created outside your function, then your function will clear out their `blah` dict. That is usually an unexpected side-effect. – BrenBarn Dec 22 '16 at 03:01
  • @BrenBarn some good insights in your answer, there a pile of knowledge I'm acquiring that I didn't have before I asked. – Dan Chrostowski Dec 22 '16 at 03:01
  • @MohammadYusufGhazi - this is perfect, thank you. Just bookmarked it for future use. – Dan Chrostowski Dec 22 '16 at 03:22