1

It seems without explicit initialization, the lists and hashs of different instances of the same class gets the same memory address. Is this correct?

#!/usr/bin/env python3

class DataManager:
    def __init__(self, name=[], value={}):
            self.parm_name = name
            self.parm_value = value
            pass

    def add_param(self, name, value):
            self.parm_name.append(name)
            self.parm_value[name] = float(value)

if __name__ == "__main__":
    dm1 = DataManager()
    dm2 = DataManager()
    print("Address of dm1: %s" % hex(id(dm1)))
    print("Address of dm2: %s" % hex(id(dm2)))
    print("Address of dm1.parm_name: %s" % hex(id(dm1.parm_name)))
    print("Address of dm2.parm_name: %s" % hex(id(dm2.parm_name)))

    dm1.parm_name.append("a")
    dm2.parm_name.append("b")

    print("dm1.parm_name: " + str(dm1.parm_name))
    print("dm2.parm_name: " + str(dm2.parm_name))

Below is the executing result:

Address of dm1: 0x7f20d9167f28
Address of dm2: 0x7f20d9167f98
Address of dm1.parm_name: 0x7f20d914bb08
Address of dm2.parm_name: 0x7f20d914bb08
dm1.parm_name: ['a', 'b']
dm2.parm_name: ['a', 'b']

What is really strange to me, that the address of dm1.parm_name is the same as that of dm2. And append actually added to the same list.

If I change the dm1 and dm2 definition as below, it will be working as expected:

dm1 = DataManager([], {})
dm2 = DataManager([], {})

Very strange, isn't it? Could anyone tell me why? Many thanks~

I also tried in Python2, the same result. versions below:

$ python3 --version
Python 3.5.0
$ python2 --version
Python 2.7.5
  • Does this answer your question? ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – EliadL Dec 04 '19 at 09:13
  • Yes, exactly, EliadL! Thanks. – Steven Ding Dec 07 '19 at 06:33

2 Answers2

2

The default arguments in the code

def __init__(self, name=[], value={})

are mutable object

Default parameters are used to enable optional parameters for calling a function, if you don't pass any parameter to __init__ the default values are used, in this case get assigned to the class fields

If default values are mutable objects they won't be refreshed for every call, python uses the same dict and list object as default params again and again and doesn't create new dict or list object for every call to __init__. It is always good practice to use non mutable objects for default parameters.

Sab
  • 485
  • 5
  • 17
  • Many thanks, Sab. After reading **“Least Astonishment” and the Mutable Default Argument**, I think I understand why. When the `mutable objects` is used as default parameter, it's evaluated during the function definition is processed. It's just like in C to allocate an object and assign the pointer to the object to the parameter, but in Python, it's processed during function definition so only done once. Any future calls to the function just operates the same object. This [link](https://stackoverflow.com/a/11416002/5319228) make me clear. – Steven Ding Dec 07 '19 at 06:32
  • Glad to hear it – Sab Dec 07 '19 at 11:29
1

See a shortened version of your code

class DataManager:
    def __init__(self, name=[]):
        self.parm_name = name

    def add_param(self, name):
        self.parm_name.append(name)


if __name__ == "__main__":
    dm1 = DataManager()
    dm2 = DataManager()

    dm1.add_param('a')
    dm2.add_param('b')

    print(dm1.parm_name)
    print(dm2.parm_name)

versus slightly modified version:

class DataManager:
    def __init__(self, name=None):
        self.parm_name = [] if name is None else name

    def add_param(self, name):
        self.parm_name.append(name)


if __name__ == "__main__":
    dm1 = DataManager()
    dm2 = DataManager()

    dm1.add_param('a')
    dm2.add_param('b')

    print(dm1.parm_name)
    print(dm2.parm_name)

In the former version dm1.parm_name is dm2.parm_name returns True.