# classmethod和staticmethod

# @classmethod

# 概述

@classmethod​是一个装饰器,用来指定一个类方法。类方法是类所拥有的方法,而不是类的实例所拥有的方法。类方法的第一个参数是一个指向类的指针,一般用cls​作为参数名。

使用@classmethod​装饰器定义的类方法,可以在类和类的实例中被调用,如下所示:

class MyClass:
    x = 0

    @classmethod
    def get_x(cls):
        return cls.x

    @classmethod
    def set_x(cls, val):
        cls.x = val

print(MyClass.get_x())  # 0
MyClass.set_x(1)
print(MyClass.get_x())  # 1

my_instance = MyClass()
print(my_instance.get_x())  # 1

从上面的代码可以看出,@classmethod​定义的方法可以直接通过类名进行调用,也可以通过实例进行调用,因为实例也是类的一种。另外,通过cls​参数可以访问和修改类的属性。

# @staticmethod

# 概述

关于staticmethod:

staticmethod的核心其实是“是不是需要建立这个类的instance”。如果希望在不建立instance的情况下调用函数,一般就是用staticmethod。如果永远是instance调用,其实就写成普通的method就好。

@staticmethod​是一个装饰器,用来指定一个静态方法。静态方法是类中的函数,不依赖于类或实例,而是在其所在的模块中定义。因此,静态方法的第一个参数没有特殊的含义,可以任意指定。

使用@staticmethod​装饰器定义的静态方法,可以在类和类的实例中被调用,如下所示:

class MyClass:
    @staticmethod
    def add(x, y):
        return x + y

print(MyClass.add(1, 2))  # 3

my_instance = MyClass()
print(my_instance.add(1, 2))  # 3

从上面的代码可以看出,静态方法和普通函数很相似,不依赖于类和实例,因此也可以直接通过类名进行调用。

# 源码层面

首先观察下面的代码以及执行结果:

def decorator(func):
    def inner(*args,**kwargs):
        print("inner",locals())
        args[0].ALL = 10086
        func(*args,**kwargs)

    return inner

class A:
    @classmethod
    @decorator
    def decdclsfun(*args,**kwargs):
        print(args[0].ALL)
        print("clsfun", "   ", locals())


    @staticmethod
    def stafun(*args,**kwargs):
        print("stafun", "   ", locals())
  
  
    def insfun(*args,**kwargs):
        print("insfun", "   ", locals())
        if len(args):
            sm = args[0]
            print(sm,type(sm))
  
    @classmethod
    def clsmethod(*args,**kwargs):
        print("cleanclsmethod", "   ", locals())
  
  

A.decdclsfun()  # inner {'args': (), 'kwargs': {}, 'func': <function A.clsfun at 0x000001B621C46048>}
                # 10086
                # decdclsfun     {'args': (<class '__main__.A'>,), 'kwargs': {}}

A.stafun()      # stafun     {'args': (), 'kwargs': {}}
A.insfun()      # insfun     {'args': (), 'kwargs': {}}
A.clsmethod()   # clsmethod     {'args': (<class '__main__.A'>,), 'kwargs': {}}

a = A()
a.decdclsfun()  # inner {'args': (), 'kwargs': {}, 'func': <function A.clsfun at 0x000001B621C46048>}
                # 10086
                # clsfun     {'args': (<class '__main__.A'>,), 'kwargs': {}}
a.stafun()      # stafun     {'args': (), 'kwargs': {}}
a.insfun()      # insfun     {'args': (<__main__.A object at 0x000001F7515BC248>,), 'kwargs': {}}
                # <__main__.A object at 0x000001F7515BC248> <class '__main__.A'>
a.clsmethod()   # clsmethod     {'args': (<class '__main__.A'>,), 'kwargs': {}}

仔细观察可以看出,实际上Python是不分实例和类这种概念的-类可以调实例方法,实例也可以调类方法。

当实例调用方法时实例会将自己作为第一个参数传递进去,而classmethod和staticmethod实际上都是在实例方法的基础之上通过装饰器实现的。

classmethod大致逻辑如下:

  • klass​​​ 在这里是一个参数,代表类本身。当类调用一个类方法时,这个参数会被自动填充为这个类的类型。如果这个类方法是通过实例进行调用的,那么这个参数会被填充为这个实例的类型。通过这个参数,我们可以在类方法中访问类本身以及类级别的属性和方法。
  • 至于为什么是类还不是实现的__call__​方法则和描述器相关。
class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

staticmethod同理,只是直接调用被修饰的函数。