How does accessing class variables from an instance work in python? – a confusing example

Question:

I have a very basic question about accessing class variables.

I thought I can reference class variables in a member function using the self keyword or the class name.

The code block below works along with my understanding

class Coo(object):
    num = 1

    def foo(self):    
        print self.num
        print Coo.num

coo = Coo()
coo.foo()

Output:
1
1

The confusion starts with below example

class Coo(object):
    num = 1

def foo(self):
    self.num = 2
    Coo.num = 3    
    print self.num
    print Coo.num

coo = Coo()
coo.foo()

Output:
2
3

The second example shows that accessing class variable using self or class name are different.

What would be the right way to access class variables in a member function?

Asked By: Kay

||

Answers:

In python everything is an object, even classes themselves.

What:

class Coo(object):
    num = 1

    def foo(self):    
        print self.num
        print Coo.num

does is that it creates a class object with the name Coo. It has attributes foo and num, with num being an int and foo being a function object.

coo = Coo()

Creates an instance object of Coo() that has the name coo. The instance coo contains no attributes:

print(coo.__dict__)
>>>{}

However, since you can do coo.foo(), or coo.num, coo clearly has attributes. The way coo gets the ability to use attributes from Coo is how python’s attribute lookup works.

For example when doing coo.num, python attempts to look up num inside of coo.__dict__, but since it cannot find num, it moves into Coo.__dict__, and finds entry {num:10}

The same thing happens when you try to call coo.foo(), coo.__dict__ has no entry for foo, but Coo.__dict__ does. coo.foo() essentially becomes Coo.foo(coo), where the instance is passed in as self. This phenomenon is what lets instances of classes use their class functions! They look it up inside of their class’s __dict__!

To use this to explain the anomaly in your question:

class Coo(object):
    num = 1

    def foo(self):    
        print self.num
        print Coo.num

coo = Coo()
coo.foo()

coo has no num attribute, so num is looked up inside Coo and they both print 10.

class Coo(object):
    num = 1

    def foo(self):
        self.num = 2
        Coo.num = 3    
        print self.num
        print Coo.num
coo = Coo()
coo.foo()

Here coo.__dict__ gains the entry {num:2} when self.num=2 is declared. Then inside of Coo.__dict__, num is set to 3.

self.num tries to look up num inside of coo.__dict__ and finds it, printing 2

Coo.num looks up num inside of Coo.__dict__ and finds it, printing 3.

As for the best way to access class variables in a member function, you should just use Classname.varname or self.__class__.varname. This guarantees that you won’t end up using the instance variable with the same name. However, it is good design to have class and instance variables have different names. This way no confusion should ever occur.

Answered By: Primusa

Python acts in a bit of a tricky way with class variables imo.
Taking this snippet of code as example :

class a:
    var = 0

    def foo(self):
        self.my_name = [k for k,v in globals().items() if v is self][0]
        print(f'----------{self.my_name}.var adress:', id(a.var))
        print(f'self.var in {self.my_name} instance:', id(self.var))

    def change(self):
        print(f'changing self.var value to 1 in {self.my_name} instance')
        self.var = 1

b = a()
c = a()
b.foo()
c.foo()

c.change()

b.foo() 
c.foo()

which outputs

----------b.var adress: 140434476089552
self.var in b instance: 140434476089552
----------c.var adress: 140434476089552
self.var in c instance: 140434476089552
changing self.var value to 1 in c instance
----------b.var adress: 140434476089552
self.var in b instance: 140434476089552
----------c.var adress: 140434476089552
self.var in c instance: 140434476089584

You can see that some write operation (self.var = 1) in the c context did create a new variable (an instance variable) for c. This is something you really have to be aware of, otherwise you won’t work with class variables but only uncorrelated instance variables.
Instead, you should always use a.var. This is also why method accessing class variables only should not have self as a parameter, to avoid this confusion.

Answered By: PoneyUHC
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.