hema chandra
hema chandra

Reputation: 420

How does @staticmethod and @classmethod act on the variable in python?

I wrote a simple program.

class Sample:

    num =45

    def __init__(self):
        print('Inside constructor')

    @classmethod
    def modifyUsingClassMethod(cls):
        cls.num = cls.num + 45

    @staticmethod
    def modifyUsingStaticMethod():
        Sample.num = Sample.num+5

s1 = Sample()
s2 = Sample()

s1.modifyUsingClassMethod()
print(s1.num, s2.num)

s1.num = s1.num + 5
print(s1.num)

s1.modifyUsingClassMethod()
print(s1.num, s2.num)

s1.modifyUsingStaticMethod()
print(s1.num, s2.num)

Output:

Inside constructor
Inside constructor
90 90
95
95 135
95 140

Can anyone explain how and why the @staticmethod and @classmethod are acting on the variable 'num'?. Why does the output shows 95,135 even after I changed the value of num using s1 instance using modifyUsingClassMethod() and why not it is updating in both cases using @staticmethod and @classmethod?

I guess when I am referring to the variable num using class object then python is treating the variable num as an instance variable but when I change the variable num using Class name then the value is not updating in the s1 but in s2. I am highly confused how @classmethod and @staticmethod works.

Upvotes: 3

Views: 2150

Answers (2)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95873

Both your class-method and your static-method only ever change the class-level variable. The issue is that you've shadowed your class-variable num inside your instance variable, s1, when you did this:

s1.num = s1.num + 5

This creates an instance variable that shadows the class variable in the instances namespace. When you access an object, the instance's namespace will be checked, if an attribute with that name isn't found, it will try the classes name-space, and then it will check the namespaces of all the classes in the method-resultion-order: the MRO (this is inheritance).

So consider your example:

In [1]: class Sample:
   ...:     num =45
   ...:
   ...:     def __init__(self):
   ...:         print('Inside constructor')
   ...:
   ...:     @classmethod
   ...:     def modifyUsingClassMethod(cls):
   ...:         cls.num = cls.num + 45
   ...:
   ...:     @staticmethod
   ...:     def modifyUsingStaticMethod():
   ...:         Sample.num = Sample.num+5
   ...:

In [2]: s1 = Sample()
   ...: s2 = Sample()
   ...:
   ...: s1.modifyUsingClassMethod()
   ...: print(s1.num,s2.num)
   ...:
   ...: s1.num = s1.num + 5
   ...: print(s1.num)
   ...:
   ...: s1.modifyUsingClassMethod()
   ...: print(s1.num,s2.num)
   ...:
   ...: s1.modifyUsingStaticMethod()
   ...: print(s1.num,s2.num)
   ...:
Inside constructor
Inside constructor
90 90
95
95 135
95 140

And now look at the objects:

In [4]: vars(Sample)
Out[4]:
mappingproxy({'__dict__': <attribute '__dict__' of 'Sample' objects>,
              '__doc__': None,
              '__init__': <function __main__.Sample.__init__>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Sample' objects>,
              'modifyUsingClassMethod': <classmethod at 0x107c3fe48>,
              'modifyUsingStaticMethod': <staticmethod at 0x107c3ff98>,
              'num': 140})

In [5]: vars(s1)
Out[5]: {'num': 95}

In [6]: vars(s2)
Out[6]: {}

You can clearly see the namespace of s1 has num in it, shadowing the one in the namespace of Sample.

Note what happens when we delete num from the instances name-space:

In [11]: del s1.num

In [12]: s1.num
Out[12]: 140

Upvotes: 3

Stephen Rauch
Stephen Rauch

Reputation: 49774

The operation:

s1.num = s1.num + 5

causes s1 to contain a local copy of .num. If you remove that from your test code, you will see that without that, the properties continue to track themselves over time.

Upvotes: 1

Related Questions