1

I'm really struggling to understand how this behavior is emerging. I've boiled it down to as small an example as possible which still produces the undesired effect.

  • I initialise an instance of a class, whose initiator specificies that if an optional argument is not passed, it should be an empty list.
  • I then add some data via a method on the class which should be acting as an instance method.
  • I create a new instance of the same class, again without passing a variable to the initiator, but this time the variable is populated as if by magic.

Code:

class Demo:
    def __init__(self, x=[]):
        self._x = x
        print x

    def add_x(self, data):
        self._x.append(data)

for x in range(0, 5):
    obj = Demo()
    obj.add_x([1, 2])

Output:

[]
[[1, 2]]
[[1, 2], [1, 2]]
[[1, 2], [1, 2], [1, 2]]
[[1, 2], [1, 2], [1, 2], [1, 2]]

What am I missing?

alexganose
  • 237
  • 4
  • 12
  • 3
    Take a look at [this SO post](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) and [this effbot article](http://effbot.org/zone/default-values.htm) – zehnpaard May 14 '15 at 12:25
  • Thanks, I did try googling for it but I couldn't find anything. I was getting increasingly and increasingly confident that I'd found something major – alexganose May 14 '15 at 12:33

2 Answers2

1

In Python if you use a = b then it doesn't creates a new object instead both point to the same memory location, And we know that lists are mutable so any changes made would be reflected in all the references to that memory location. Now you can use [:] if you have a linear list or you can also use deepcopy() in case there are nested lists.

class Test:
    def __init__(self, x=list()):
        self._x = x[:]    #Here is the magic
        print x

    def add_x(self, data):
        self._x.append(data)
ZdaR
  • 22,343
  • 7
  • 66
  • 87
1
def __init__(self, x=[]):
    self._x = x

In this case, whenever you initialize an instance of your class with no argument, self._x will reference the same object instead of creating a new empty list for each instance. In effect, self._x.append is appending to the same list.

The recommended way to do this is

def __init__(self, x=None):
    if x is None:
        # create a new list
        x = []
    self._x = x

This applies to all mutable objects (such as dict and set...).

sirfz
  • 4,097
  • 23
  • 37