Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling class variable with self

How would you I came up with this interesting (at least to me) example.

import numpy as np


class Something(object):
    a = np.random.randint(low=0, high=10)

    def do(self):
        self.a += 1
        print(self.a)

if __name__ == '__main__':
    something = Something()
    print(something.__str__())
    something.do()
    something2 = Something()
    print(something2.__str__())
    something2.do()
    something3 = Something()
    print(something3.__str__())
    something3.do()

The above prints the following in the console:

$ python test.py
<__main__.Something object at 0x7f03a80e0518>
1
<__main__.Something object at 0x7f03a80cfcc0>
1
<__main__.Something object at 0x7f03a80cfcf8>
1

I'm a bit confused because I (wrongly) assumed the value of a would have increased.

I'm able to obtain the behaviour I would expect if I use the @classmethod decorator.

import numpy as np


class Something(object):
    a = np.random.randint(low=0, high=10)

    @classmethod
    def do(cls):
        cls.a += 1
        print(cls.a)

if __name__ == '__main__':
    something = Something()
    print(something.__str__())
    something.do()
    something2 = Something()
    print(something2.__str__())
    something2.do()
    something3 = Something()
    print(something3.__str__())
    something3.do()

This correctly prints the following in the console.

python test.py
<__main__.Something object at 0x7faac77becc0>
3
<__main__.Something object at 0x7faac77becf8>
4
<__main__.Something object at 0x7faac77c3978>
5

Now, I'm wondering in the first example, when I'm calling self.a, what I'm accessing? It's not a class variable since I don't seem to be able to change its value. It's not an instance variable either, since this seems to be shared among different objects of the same class. How would you call it?

Is this a class variable that I'm using in the wrong way? I know the cls name if a convention, so maybe I'm truly accessing a class variable, but I'm not able to change its value because I haven't decorate the method with the @classmethod decorator.

Is this a sort of illegitimate use of the language? I mean something it's best practice to not do in order to avoid introduce a bug on a later stage?

like image 934
Gianluca Avatar asked Dec 19 '22 09:12

Gianluca


2 Answers

What is happening is that self.a refers to two things at different times.

When no instance variable exists for a name, Python will lookup the value on the class. So the value retrieved for self.a will be the class variable.

But when setting an attribute via self, Python will always set an instance variable. So now self.a is a new instance variable whose value is equal to the class variable + 1. This attribute shadows the class attribute, which you can no longer access via self but only via the class.

(One minor point, which has nothing to do with the question: you should never access double-underscore methods directly. Instead of calling something2.__str__(), call str(something2) etc.)

like image 87
Daniel Roseman Avatar answered Dec 28 '22 05:12

Daniel Roseman


Answer by Daniel Roseman clearly explains the problem. Here are some additional points and hope it helps. You can use type(self).a instead of self.a. Also look at the discussions Python: self vs type(self) and the proper use of class variables and Python: self.__class__ vs. type(self)

import numpy as np


class Something(object):
    a = np.random.randint(low=0, high=10)

    def do(self):
        type(self).a += 1
        print(type(self).a)

if __name__ == '__main__':
    something = Something()
    print(str(something ))
    something.do()
    something2 = Something()
    print(str(something2))
    something2.do()
    something3 = Something()
    print(str(something3))
    something3.do()
like image 28
abcabc Avatar answered Dec 28 '22 06:12

abcabc