Here's a simplified version of the code in question:
class thing:
def __init__(self, data1, data2={'foo': 1}):
self.data2 = data2
self.data2['bar'] = data1
datas = ['FIRST', 'SECOND']
things = [thing(x) for x in datas]
for p in things:
print p.data2['bar']
I would expect this code to return:
FIRST
SECOND
However, it actually returns:
SECOND
SECOND
Why?
My best guess is:
- that I am creating a single dictionary
ur_dict = {'foo': 1}
, - that when I initialize an object of class
thing
I am not creating a new dictionaryself.data2={'foo': 1}
but rather initializing a reference tour_dict
, - and that when, in the constructor, I add the key-value pair
bar: data1
toself.data2
, I'm actually adding that key and value tour_dict
itself. This would update thedata2
field of every single object in the list.
I tested this hypothesis by appending the following snippet to the code above:
things[0].data2['bar'] = 'FIRST'
for p in things:
print p.data2['bar']
Sure enough, the result is:
FIRST
FIRST
So I think my hypothesis is probably correct, but it's still mysterious to me. My question seems very related to this question --- when I create a default value in a constructor, is this value a class variable instead of an instance variable? Why doesn't python create new objects for default values of arguments in an object? Is there a good way to conceptualize or to catch this kind of error in the future? What are some good references to learn about what's going on here?
(Apologies in advance if I mangled jargon; I'm a mathematician by formal education.)
Edit: @CrazyPython[::-1]
mentioned this is a property of functions, not classes or objects. I created an example of a function with a mutable default argument and I'm trying to figure out how to break it in the manner I experienced with objects and classes above. Came up with this:
EXPONENT = 1
def fooBar(x, y=EXPONENT):
return x**y
print EXPONENT
print foo(5)
EXPONENT = 2
print EXPONENT
print foo(5)
However, it returns:
1
5
2
5
What's a good way to "break" this to illustrate why not to use a mutable default argument here?