Search code examples
pythonoopstatic-methodsclass-method

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.


Solution

  • 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