-2

I'm having a problem understanding the following:

    class Test():
        def __init__(self, data = []):
            self.data = data

    a = Test()
    b = Test()
    a.data.append(1)
    print(b.data) # prints [1]

    class Test1():
        def __init__(self, data):
            self.data = data

    a = Test1([])
    b = Test1([])
    a.data.append(1)
    print(b.data) # prints []


    class Test2():
        def __init__(self, data = 1):
            self.data = data

    a = Test2()
    b = Test2()
    a.data = 2
    print(b.data) # prints 1

It prints out [1] I was expecting that the instance variable data of b would be an empty list, because it is a instance variable and not a class variable! If I do the same thing without a default parameter and just pass an empty list to the parameter data it works. It also works with an int as a default parameter. I know lists are passed by reference, but that shouldn't be happening anyways. How? why? Am I not seeing something here??

  • 1
    There is an explanation of why that happens here: https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments – Simeon Visser May 10 '19 at 20:20

2 Answers2

0

Your default argument for data is a mutable type! When python is creating the definition of your Test class, it only makes one instance of the default arguments. This means that every time you create a Test object, it's data property will be pointing to the same list. After you create both of your Test objects, both of their .data properties point to the exact same list in memory. If you change that list once, it will change it for all your Test classes. The list.append method modifies the list in place, hence why you see this behavior.

Notice in your Test1 case, you are creating two different empty lists and assigning them to the .data property. Since these are two separate lists, you can change either one however you please without affecting the other.

Python int's are not mutable, and even if they were, you are overwriting the .data property in your Test2 example so you get the expected behavior.

But sometimes, your function needs the default argument to be a list or some other mutable type! What you can do is assign the default value to None, and then check if the value is None inside the function. If it is, assign your property to your actual desired default value. Like this:

class Test:
    def __init__(self, data = None):
        self.data =  [ ] if data is None else data
        # Set self.data to an empty list if data is none, otherwise, set it to whatever data already is.

a = Test()
b = Test()
a.data.append(1)
print(f"a.data:{a.data}, b.data:{b.data}")
# >> a.data:[1], b.data:[]
SyntaxVoid
  • 2,501
  • 2
  • 15
  • 23
-1

Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

https://docs.python-guide.org/writing/gotchas/