-1

Consider the following code in Python 3.5.2:

class NewObj():

    def __init__(self, refs={}):
        self.refs = refs

class ObjA(NewObj):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class ObjB(NewObj):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.refs['c'] = 3

a = ObjA(refs={'a':1, 'b': 2})
b = ObjA()
c = ObjB()
lst = [a, b, c]

for obj in lst:
    print('%s has refs: %s' % (obj, obj.refs))

The output of the code is:

<__main__.ObjA object at 0x7f74f0f369b0> has refs: {'a': 1, 'b': 2}
<__main__.ObjA object at 0x7f74f0f36a90> has refs: {'c': 3}
<__main__.ObjB object at 0x7f74f0f36ac8> has refs: {'c': 3}

It is the second line of output that is causing me confusion - it seems to me that an empty dictionary should be output. The reason being that because b is assigned an instance of ObjA without any arguments being called, b.refs == {} should be True, as per the default initialisation.

Is this a bug or desired behaviour? If it's not a bug, could I please get an explanation why this is desired behaviour, and the most minimal change to the code to get the output I intend (i.e. when no arguments are provided, .refs is initialised to an empty dict)?

Charlie
  • 469
  • 4
  • 15
  • This is a well known situation where new Python programmers can get surprised. The default argument you declare with `refs={}` is created just once, when the function is defined. All calls that use the default value get a reference to the same dictionary (not a new empty dictionary for each of them). – Blckknght Jul 19 '17 at 02:37

1 Answers1

1

As @blckknght correctly pointed out, this is actually intentional behaviour and a duplicate. However, the secondary part of the question - about minimal necessary change - is not easily found in the duplicate. As per the documentation:

Default parameter values are evaluated from left to right when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended. A way around this is to use None as the default, and explicitly test for it in the body of the function, e.g.:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin
Charlie
  • 469
  • 4
  • 15