Why is classmethod more popular than static method?

Posted by thegreatdanton on Thu, 30 Dec 2021 19:48:58 +0100

We know that both classmethod and staticmethod can be used as decorators of functions and can be used for methods that do not involve class member variables. However, if you check the Python standard library, you will know that classmethod (1052) is used much more than staticmethod(539). Why?

This starts with the difference between static method and classmethod.

1. In terms of calling form, the two are similar in usage

First, let's talk about what is a class and what is an instance. For example, a = A(), then a is a class and a is an instance.

In terms of definition form, the first parameter of clasmethod is cls, which represents the class itself. The first parameter of ordinary method is self, which represents the instance itself. The parameters of staticmethod are no different from ordinary functions.

In terms of call form, both staticmethod and classmethod support class direct call and instance call.

class MyClass:
    def method(self):
        """
        Instance methods need a class instance and
        can access the instance through `self`.
        """
        return 'instance method called', self

    @classmethod
    def classmethod(cls):
        """
        Class methods don't need a class instance.
        They can't access the instance (self) but
        they have access to the class itself via `cls`.
        """
        return 'class method called', cls

    @staticmethod
    def staticmethod():
        """
        Static methods don't have access to `cls` or `self`.
        They work like regular functions but belong to
        the class's namespace.
        """
        return 'static method called'

# All methods types can be
# called on a class instance:
>>> obj = MyClass()
>>> obj.method()
('instance method called', <MyClass instance at 0x1019381b8>)
>>> obj.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)
>>> obj.staticmethod()
'static method called'

# Calling instance methods fails
# if we only have the class object:
>>> MyClass.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)
>>> MyClass.staticmethod()
'static method called'

2. Let's talk about static method first.

If staticmethod is added to a class function, it usually means that the calculation of this function does not involve class variables and can be used without class instantiation, that is, the relationship between this function and this class is not very close. In other words, functions decorated with staticmethod can also be defined outside the class. I sometimes wonder whether to use staticmethod in a class or utils Write a separate function in py? For example, the following Calendar class:

class Calendar:
    def __init__(self):
        self.events = []

    def add_event(self, event):
        self.events.append(event)

    @staticmethod
    def is_weekend(dt:datetime):
        return dt.weekday() > 4

if __name__ == '__main__':
    print(Calendar.is_weekend(datetime(2021,12,27)))
    #output: False

The function is_ Weeken is used to determine whether a day is a weekend. It can be defined outside Calendar as a public method, so that the class name Calendar does not need to be added when using this function.

However, some cases are best defined in the class, that is, this function leaves the context of the class and does not know how to call it. For example, the following class is used to judge whether the matrix can be multiplied. The more readable call form is matrix can_ multiply:

from dataclasses import dataclass
@dataclass
class Matrix:
    shape: tuple[int, int] # python3. The writing of this type declaration is supported after 9

    @staticmethod
    def can_multiply(a, b):
        n, m = a.shape
        k, l = b.shape
        return m == k

3. Say classmethod again.

First, we understand it from the form of clasmethod. Its first parameter is cls, which represents the class itself. That is, we can call the class constructor cls() in the classmethod function to generate a new instance. From this point, we can infer its usage scenario:

  • When we need to call the constructor again, that is, when we create a new instance object
  • You need to return a new instance without modifying the existing instance

For example, the following code:

class Stream:

    def extend(self, other):
        # modify self using other
        ...

    @classmethod
    def from_file(cls, file):
        ...

    @classmethod
    def concatenate(cls, *streams):
        s = cls()
        for stream in streams:
            s.extend(stream)
        return s

steam = Steam()

When we call steam The extend function will modify the steam itself, while calling concatenate will return a new instance object without modifying the steam itself.

4. Essential difference

We can try to implement the two decorators of classmethod and staticmethod to see their essential differences:

class StaticMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        return self.func

    def __call__(self, *args, **kwargs):  # New in Python 3.10
        return self.func(*args, **kwargs)


class ClassMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        return self.func.__get__(owner, type(owner))

class A:
    def normal(self, *args, **kwargs):
        print(f"normal({self=}, {args=}, {kwargs=})")

    @staticmethod
    def f1(*args, **kwargs):
        print(f"f1({args=}, {kwargs=})")

    @StaticMethod
    def f2(*args, **kwargs):
        print(f"f2({args=}, {kwargs=})")

    @classmethod
    def g1(cls, *args, **kwargs):
        print(f"g1({cls=}, {args=}, {kwargs=})")

    @ClassMethod
    def g2(cls, *args, **kwargs):
        print(f"g2({cls=}, {args=}, {kwargs=})")


def staticmethod_example():
    A.f1()
    A.f2()

    A().f1()
    A().f2()

    print(f'{A.f1=}')
    print(f'{A.f2=}')

    print(A().f1)
    print(A().f2)

    print(f'{type(A.f1)=}')
    print(f'{type(A.f2)=}')


def main():
    A.f1()
    A.f2()

    A().f1()
    A().f2()

    A.g1()
    A.g2()

    A().g1()
    A().g2()

    print(f'{A.f1=}')
    print(f'{A.f2=}')

    print(f'{A().f1=}')
    print(f'{A().f2=}')

    print(f'{type(A.f1)=}')
    print(f'{type(A.f2)=}')


    print(f'{A.g1=}')
    print(f'{A.g2=}')

    print(f'{A().g1=}')
    print(f'{A().g2=}')

    print(f'{type(A.g1)=}')
    print(f'{type(A.g2)=}')

if __name__ == "__main__":
 main()

The above class staticmethod is equivalent to decorator staticmethod, and class classmethod is equivalent to decorator classmethod. The execution result of the code is as follows:

It can be seen that the functions of staticmethod and classmethod are the same as those of the standard library. It can also be seen that the difference between classmethod and staticmethod is that classmethod contains class information and can call the constructor of class, which has better expansibility in programming.

Last words

To answer the initial question of this article, why is classmethod more favored by the standard library? Because classmethod can replace static method, but not vice versa. In other words, where staticmethod is used, replace staticmethod with classmethod, and then add the first parameter cls to the function. The code called later can remain unchanged, but not vice versa. That is to say, classmethod has better compatibility.

On the other hand, classmethod can call the constructor of the class again internally, and generate a new instance without modifying the existing instance. It has stronger flexibility and scalability, so it is more favored. Of course, this is just my humble opinion. If you have different ideas, you can Message discussion Ha.