10

Could somebody please explain the following code to me.

class InnerTest:

    def __init__(self, value = 0):
        self.value = value

class OuterTest:

    def __init__(self, inner_test = InnerTest()):
        self.inner_test = inner_test

a = OuterTest()
b = OuterTest()

a.inner_test.value = 42
print b.inner_test.value

It prints 42, I expected 0.

I meant to create two instances of OuterTest, which would contain a distinct instance of InnerTest each. Instead I got two instances of OuterTest which reference the same instance of InnerTest.

Also what would be a correct way to implement what I wanted please?

marcv81
  • 850
  • 8
  • 22

2 Answers2

10

The default parameter in functions are evaluated only once, at the time of function definition. So there is only one instance of InnerTest being used for both the object.

That means, when you create the two objects:

a = OuterTest()
b = OuterTest()

Both, a.inner_test and b.inner_test, are referring to the same instance, and hence the result.

To solve this, change the default value to None, and create instance conditionally:

class OuterTest:
    def __init__(self, inner_test=None):
        if not inner_test:
            inner_test = InnerTest()
        self.inner_test = inner_test
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • 1
    @PadraicCunningham You don't have to pass an argument necessarily. In which case, it would be `None`, and then you can initialize it in the function. – Rohit Jain Dec 15 '15 at 16:18
  • 1
    @PadraicCunningham No, the issue is not the same. If the value is `None`, in both `a` and `b`, this will create 2 different instances of `InnerTest`, inside the `if`. – Rohit Jain Dec 15 '15 at 16:19
  • This is the correct answer: it explains what is happening, and provides a practical solution to create distinct instances while retaining the convenience of default parameters. Thanks. – marcv81 Dec 16 '15 at 01:47
2

You could move the InnerTest() into the init or call it in the init, passing a reference to the class n the second case.

Using def __init__(self, inner_test = InnerTest()): is evaluated once so the object is shared among all instances of the OuterTest class making it a class attribute as opposed to an instance attribute by instantiating it in the init.

class OuterTest:
    def __init__(self):
        self.inner_test = InnerTest()

Or:

class OuterTest:
    def __init__(self, inner_test = InnerTest):
        self.inner_test = inner_test()

Both methods will work as desired, passing a reference to a class means you have the option of passing in whatever class you want:

In [11]: class OuterTest:
   ....:     def __init__(self, inner_test=InnerTest):
   ....:             self.inner_test = inner_test()
   ....:         

In [12]: a = OuterTest()

In [13]: b = OuterTest()

In [14]: a.inner_test.value = 42

In [15]: print(a.inner_test.value)
42

In [16]: print(b.inner_test.value)
0

In [17]: class OuterTest:
   ....:     def __init__(self):
   ....:             self.inner_test = InnerTest()
   ....:         

In [18]: a = OuterTest()

In [19]: b = OuterTest()

In [20]: a.inner_test.value = 42

In [21]: print(a.inner_test.value)
42

In [22]: print(b.inner_test.value)
Community
  • 1
  • 1
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • The second solution is not what I want, but the first one works well. Would you have any idea why my original code does not do what I want? – marcv81 Dec 15 '15 at 16:10
  • Making the inner_text parameter optional means that he could want to pass a instantiated object as parameter. It would work with your answer ? – André Roggeri Campos Dec 15 '15 at 16:10
  • @marcv81, it is essentially the difference between a class an instance attribute – Padraic Cunningham Dec 15 '15 at 16:11
  • In the first solution, you remove the option of passing an Innertest object though.. – Lisa Dec 15 '15 at 16:11
  • @Lisa, there are two options, both are to demonstrate that the instance needs to be instantiated in the init method – Padraic Cunningham Dec 15 '15 at 16:12
  • @PadraicCunningham: Thanks for your contribution. I picked the other answer as it provides a practical solution to retain the convenience of default parameters. Also (before your last edit) your answer did not provide an explanation about what is actually happening. Indeed I learned that default function parameters are hidden class variables. – marcv81 Dec 16 '15 at 01:56