>>> from env_helper import info; info()
页面更新时间: 2024-01-20 22:03:49
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-17-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.17. 分清staticmethod和classmethod的适用场景

Python中的静态方法(staticmethod)和类方法(classmethod)都依赖于装饰器 (decorator)来实现。其中静态方法的用法如下:

class C (object):

&staticmethod def f (argl, arg2 P ...):

而类方法的用法如下:

class C (object):

@classmethod def f (clsr argl, arg2, "•):

静态方法和类方法都可以通过 类名.方法名 (如 C.f() )或者 实例.方法名C().f() )的形 式来访问。 其中静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则, 而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。

>>> class A (object):
>>>     def instance_method(self , x):
>>>         print ("calling instance method instance_method (%s, %s) "%(self, x))
>>>     @classmethod
>>>     def class_method (cls,x):
>>>         print( "calling class_method(%s,%s)"%(cls,x))
>>>     @staticmethod
>>>     def static_method(x):
>>>         print ("calling static_method(%s)"%x)
>>> a = A()
>>> a.instance_method("test")
calling instance method instance_method (<__main__.A object at 0x7f726440b110>, test)
>>> a.class_method("test")
calling class_method(<class '__main__.A'>,test)
>>> a.static_method("test")
calling static_method(test)

上面的例子是类方法和静态方法的简单应用,从程序的输出可以看出虽然类方法在调用 的时候没有显式声明cls,但实际上类本身是作为隐含参数传人的。

在了解完静态方法和类方法的基本知识之后再来研究这样一个问题:为什么需要静态方 法和类方法,它们和普通的实例方法之间存在什么区别?我们通过对具体问题的研究来回答 这些问题。,假设有水果类Fruit,它用属性total表示总量,Fruit中已经有方法set()来设置总 量,print_total()方法来打印水果数Se类Apple和类Orange继承自Fruit。我们需要分別跟 踪不同类型的水果的总M。有好几种方法可以实现这个功能。

方法一: 利用普通的实例方法来实现。

在Apple和Orange类中分別定义类变量total,然后再植盖基类的set()和print_total()方 法,但这会导致代码冗余,因为本质上这些方法所实现的功能相同(读者可以自行完成)。

方法二: 使用类方法实现,具体实现代码清单如下。

>>>
>>> class Fruit(object):
>>>
>>>     def __init__ (self, area="", category="",batch=""):
>>>         self.area = area
>>>         self.category = category
>>>         self . batch = batch
>>>         @staticmethod
>>>         def Init_Product(product_info):
>>>             area, category, batch = map(int, product_info.split('-'))
>>>             fruit = Fruit (area, category, batch)
>>>             return fruit
>>>
>>>     total = 0
>>>     @classmethod
>>>     def print_total(cls):
>>>         print (cls.total) #
>>>         print (id(Fruit.total))
>>>         print (id(cls.total))
>>>     @classmethod
>>>     def set (cls, value):
>>>     #print *'calling class^method (%s^ %s) w% (clsr value)
>>>         cls.total = value
>>> class Apple(Fruit):
>>>     pass
>>> class Orange(Fruit):
>>>     pass
>>> appl = Apple ()
>>> appl.set (200)
>>> app2 = Apple()
>>> org1= Orange()
>>> org1.set(300)
>>> org2 = Orange()
>>> appl.print_total ()     #output 200
>>> org1.print_total() #foutput 300
200
10861160
10867560
300
10861160
140129284973232

简单分析可知,针对不同种类的水果对象调用set()方法的时候隐形传人的参数为该对象 所对应的类,在调用set()的过程中动态生成了对应的类的类变量。这就是classmethod的妙 处。请读者自行思考:此处将类方法改为静态方法是否可行呢?

我们再来看一个必须使用类方法而不是静态方法的例子:假设对于每一个Fruit类我们 提供3个实例属性;area表示区域代码,category表示种类代码,batch表示批次号。现需要一个方法能够将以area-category-batch形式表示的字符串形式的输人转化为对应的属性并以对象返回。

假设Fruit中有如下初始化方法,并且有静态方法Init_Product()能够满足上面所提的 要求。

>>>
>>> def __init__ (self, area="", category="",batch=""):
>>>     self.area = area
>>>     self.category = category
>>>     self . batch = batch
>>>     @staticmethod
>>>     def Init_Product(product_info):
>>>         area, category, batch = map(int, product_info.split('-'))
>>>         fruit = Fruit (area, category, batch)
>>>         return fruit

我们首先来看看使用静态方法所带来的问题。

app1=Apple(2,5,10) org1=Orange.Init_Product("3-3-9")

print appl is instance of Apple: w+str (i$instance appl, Apple)) print orgl is instance of Orange; w+str {isixistance (orgl , Orange))

运行程序我们会发现isinstance(org1,Orange)的值为False。这不奇怪,因为静态方法实 际相当于一个定义在类里面的函数,Init_Product返冋的实际是Fruit的对象,所以它不会是 Orange的实例。Init_Product()的功能类似于工厂方法,能够根据不同的类型返回对应的类的 实例.因此使用静态方法并不能获得期望的结果,类方法才是正确的解决方案。可以针对代码做出如下修改:

>>>
>>> @classmethod
>>> def Init_Product(els,rproductinfo):
>>>     area, category, batch = map(int, product_info.split("-"))
>>>     fruit =els(arear ,category, batch)
>>>     return fruit

也许读者会问:既然这样,静态方法到底有什么用呢?什么情况下可以使用静态方法? 继续上面的例子.假设我们还需要一个方法来验证输人的合法性,方法的具体实现如下:

>>>
>>> def is_input_valid(product_info):
>>>     area, category, batch = map(inproduct_info.split('-'))
>>>     try:
>>>         assert 0 <= area <=10
>>>         assert 0 <=category <= 15
>>>         assert 0 <= batch <= 99
>>>     except AssertionError:
>>>         return False
>>>     return True

那么应该将其声明为静态方法还是类方法呢?答案是两者都可,甚至将其作为一个定义 在类的外部的函数都是可以的。但仔细分析该方法会发现它既不跟特定的实例相关也不跟特 定的类相关,因此将其定义为静态方法是个不错的选择,这样代码能够一目了然。也许你会 问:为什么不将该方法定义成外部函数呢?这是因为静态方法定义在类中,较之外部函数. 能够史加有效地将代码组织起来,从而使相关代码的垂直距离更近,提高代码的可维护性。 当然,如果有一组独立的方法.将其定义在一个模块中,通过模块来访问这些方法也是一个 不错的选择。