[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Instance vs Class variable oddity

> On May 15, 2019, at 11:02 AM, Rob Gaddi <rgaddi at highlandtechnology.invalid> wrote:
> On 5/15/19 10:52 AM, Irv Kalb wrote:
>> I just saw some code that confused me.  The confusion has to do with class variables and instance variables.  In order to understand it better, I built this very small example:
>> class Test:
>>     x = 0
>>     def __init__(self, id):
>>         self.id = id
>>         self.show()
>>     def show(self):
>>         print('ID:', self.id, 'self.x is:', self.x)
>>     def increment(self):
>>         self.x = self.x + 1
>> # Create two instances of the Test object, each shows itself
>> t1 = Test('first')
>> t2 = Test('second')
>> # Ask t1 to increment itself twice
>> t1.increment()
>> t1.increment()
>> # Ask each to show themselves
>> t1.show()
>> t2.show()
>> # Let's see what the class has
>> print('Test.x is:', Test.x)
>> When I instantiate two objects (t1 and t2), the __init__ method calls the show method, which prints a value of self.x.  I'm not understanding why this is legal.  I would expect that I would get an error message saying that self.x does not exist, since no instance variable named self.x has been defined.
>> However, this code runs fine, and gives the following output:
>> ID: first self.x is: 0
>> ID: second self.x is: 0
>> ID: first self.x is: 2
>> ID: second self.x is: 0
>> Test.x is: 0
>> My guess is that there is some scoping rule that says that if there is no instance variable by a given name, then see if there is one in the class.  If that is the case, then this line in the increment method seems odd:
>> self.x = self.x + 1
>> If the self.x on the right hand side refers to the class variable, and creates an instance variable called self.x on the left hand side, then how does the second call work using the value of the instance variable on the right hand side?  Can someone explain what's going on here?
> Pretty much exactly like that.  The first time you call t1.increment, you fail to find x in the instance dictionary, fall back and find it equal to 0, add 1 makes 1, and store x=1 into the instance dictionary.
> The second time, you find x=1 in the instance dictionary, add 1 makes 2, and overwrite x=2 into the instance dictionary.
> You never call increment on t2, so it never gets an x in its instance dictionary, so t2.x still refers to Test.x.
>> Disclaimer:  I would never write code like this, but I saw this in someone else's code and didn't understand how it was working.
> Sometimes when I'm feeling lazy it's a convenient way to set defaults on instance variables.  I don't love it, but I also can't explain what I find wrong with it.
>> Irv

Thanks for the confirmation.   I still think its weird to have:

   self.x = self.x + 1

and have it refer to two potentially different variables at different times, but at least I understand what's going on.