当前位置: 首页 > 科技观察

为什么Classmethod比Staticmethod更受欢迎?

时间:2023-03-15 13:28:40 科技观察

我们知道classmethod和staticmethod都可以作为函数的装饰器,都可以用于不涉及类的成员变量的方法,但是可以查一下Python标准库就知道classmethod使用的是多少次数(1052)远远超过静态方法(539)。为什么?这就要从staticmethod和classmethod的区别说起。1、从调用形式来看,两者的用法几乎是一样的。首先说一下什么是类,什么是实例。比如a=A(),那么A就是一个类,a就是一个实例。从定义形式上看,clasmethod的第一个参数是cls,代表类本身,普通方法的第一个参数是self,代表实例本身,staticmethod的参数和普通函数没有区别。从调用形式上看,staticmethod和classmethod都支持类直接调用和实例调用。classMyClass:defmethod(self):"""Instancemethodsneedaclassinstanceandcanaccesstheinstancethrough`self`."""return'instancemethodcalled',self@classmethoddefclassmethod(cls):"""Classmethodsdon'tneedaclassinstance.Theycan'taccesstheinstance(self)buttheyhaveaccesstotheclassitselfvia`cls`."""return'classmethodcalled',cls@staticmethoddefstaticmethod():"""静态方法不能访问`cls`或`self`。它们像常规函数一样工作,但属于类的命名空间。"""return'staticmethodcalled'#Allmethodstypescanbe#callonaclassinstance:>>>obj=MyClass()>>>obj.method()('instancemethodcalled',)>>>obj.classmethod()('classmethodcalled',)>>>obj.staticmethod()'staticmethodcalled'#Callinginstancemethodsfails#ifweonlyhavetheclassobject:>>>MyClass.classmethod()('类方法odcalled',)>>>MyClass.staticmethod()'staticmethodcalled'2、先说staticmethod。如果在类函数中加入staticmethod,通常意味着这个函数的计算不涉及类变量,不需要类的实例化即可,也就是说函数与类的关系不是很近。也就是说,用staticmethod修饰的函数也可以定义在类外。有时候会想是在类中使用staticmethod还是在utils.py中写一个函数?例如,以下日历类:classCalendar:def__init__(self):self.events=[]defadd_event(self,event):self.events.append(event)@staticmethoddefis_weekend(dt:datetime):returndt.weekday()>4if__name__=='__main__':print(Calendar.is_weekend(datetime(2021,12,27)))#output:False里面的函数is_weekend是用来判断某一天是否是周末的,可以在外面定义Calendar作为一个公共方法,这样使用这个函数时就不需要加上类名Calendar。但是有些情况下,还是定义在类中比较好,就是这个函数离开了类的上下文,不知道怎么调用。比如下面这个类就是用来判断矩阵是否可以相乘的。比较可读的调用形式是Matrix.can_multiply:fromdataclassesimportdataclass@dataclassclassMatrix:shape:tuple[int,int]#Python3.9支持这种声明的写法@staticmethoddefcan_multiply(a,b):n,m=a.shapek,l=b.shapereturnm==k3,说说classmethod。首先我们从clasmethod的形式来理解。它的第一个参数是cls,代表类本身。也就是说,我们可以在classmethod函数中调用类的构造函数cls()来生成一个新的实例。从这一点我们可以推断出它的使用场景:当我们需要再次调用构造函数时,即创建一个新的实例对象时,需要在不修改已有实例的情况下返回一个新的实例,比如下面的代码:classStream:defextend(self,other):#modifyselfusingother...@classmethoddefrom_file(cls,file):...@classmethoddefconcatenate(cls,*streams):s=cls()forstreaminstreams:s.extend(stream)返回steam=Steam()当我们调用steam.extend函数时,我们会修改steam本身,而当我们调用concatenate时,会在不修改steam本身的情况下,返回一个新的实例对象。4.本质区别我们可以尝试实现classmethod和staticmethod这两个装饰器,看看它们的本质区别:*args,**kwargs):#NewinPython3.10返回self.func(*args,**kwargs)classClassMethod:def__init__(self,func):self.func=funcdef__get__(self,instance,owner):returnsself.func。__get__(owner,type(owner))classA:defnormal(self,*args,**kwargs):print(f"normal({self=},{args=},{kwargs=})")@staticmethoddeff1(*args,**kwargs):print(f"f1({args=},{kwargs=})")@StaticMethoddeff2(*args,**kwargs):print(f"f2({args=},{kwargs=})")@classmethoddefg1(cls,*args,**kwargs):print(f"g1({cls=},{args=},{kwargs=})")@ClassMethoddefg2(cls,*args,**kwargs):print(f"g2({cls=},{args=},{kwargs=})")defstaticmethod_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)=}')defmain():A.f1()A.f2()A().f1()A().f2()A.g1()A.g2()A().g1()A().g2()打印(f'{A.f1=}')打印(f'{A.f2=}')打印(f'{A().f1=}')打印(f'{A().f2=}')打印(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()上面的类StaticMethod相当于装饰器staticmethod,类ClassMethod相当于装饰器classmethod。代码执行结果如下:可以看出,StaticMethod和ClassMethod的功能与标准库的功能相同。也可以看出,classmethod与staticmethod的区别在于,classmethod具有类信息,可以调用类的构造函数,在编程上具有更好的可扩展性。最后回答一下本文最初的问题,为什么classmethod更受标准库青睐?因为classmethod可以代替staticmethod的作用,反之则不行。也就是说,在使用staticmethod的地方,把staticmethod换成classmethod,然后在函数中加上第一个参数cls,后面调用的代码可以保持不变,反之则不行,也就是说classmethod的兼容性更好。另一方面,classmethod可以在内部再次调用类的构造函数,可以在不修改现有实例的情况下生成新的实例。它具有更强的灵活性和可扩展性,因此更受青睐。当然,这只是我的拙见。如果有不同的想法,可以留言讨论。本文转载自微信公众号“Python7号”,可通过以下二维码关注。转载本文请联系Python七号公众号。