Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python function "remembering" earlier argument (**kwargs)

I have some objects that have a dictionary of attributes, obj.attrs. The constructor for these objects accepts a dict and/or **kwargs, for convenience.

It looks a little like this:

class Thing:
    def __init__(self, attrs={}, **kwargs):
        for arg in kwargs:
            attrs[arg] = kwargs[arg]
        self.attrs = attrs

Such that Thing({'color':'red'}) does the same as Thing(color='red').

My problem is that the constructor somehow remembers the last attrs value passed to it.

For example:

>>> thing1 = Thing(color='red')
>>> thing2 = Thing()
>>> thing2.attrs
{'color': 'red'}

...but thing2.attrs should just be an empty dict! {}

This made me wonder if this isn't a problem with using both **kwargs and an argument like attrs={}.

Any ideas?

like image 544
tjvr Avatar asked Dec 04 '22 21:12

tjvr


2 Answers

The problem with using default arguments is that only one instance of them actually exists. When you say attrs={} in your init method definition, that single default {} instance is the default for every call to that method (it doesn't make a new default empty dict every time, it uses the same one).

The problem is that if there's only one attrs in existence and then for every instance of Thing you say self.attrs = attrs, the self.attrs member variable for every single one of your instance is pointing to the single shared default instance of attrs.

The other question is, isn't this completely redundant? You can use **kwargs to pass in keyword/value args or a dictionary. If you just defined this:

class Thing:
    def __init__(self, **kwargs):
        for arg in kwargs:
            self.attrs[arg] = kwargs[arg]

All of these strategies still work:

thing1 = Thing(color='red')

thing2 = Thing(**{'color':'red'})

my_dict = {'color' : 'red'}
thing3 = Thing(**my_dict)

So if you simply define and use Thing that way, you'll avoid your problem altogether.

like image 82
Brent Writes Code Avatar answered Dec 29 '22 04:12

Brent Writes Code


attrs is a reference to a dictionary. When you create a new object, self.attrs points to that dictionary. When you assign a value from a kwargs, it goes to this dictionary.

Now, when you create a second instance, it's self.attrs also points to that same dictionary. Thus, it gets whatever data is in that dictionary.

For a nice discussion of this see "Least Astonishment" in Python: The Mutable Default Argument here on stackoverflow. Also see Default Parameter Values in Python on effbot.

like image 31
Bryan Oakley Avatar answered Dec 29 '22 06:12

Bryan Oakley