返回介绍

建议35:分清 staticmethod 和 classmethod 的适用场景

发布于 2024-01-30 22:19:09 字数 4056 浏览 0 评论 0 收藏 0

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

class C(object):
  @staticmethod
  def f(arg1, arg2, ...):

而类方法的用法如下:

class C(object):
  @classmethod
  def f(cls, arg1, 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 0x00D66B50>,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()方法来打印水果数量。类Apple和类Orange继承自Fruit。我们需要分别跟踪不同类型的水果的总量。有好几种方法可以实现这个功能。

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

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

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

class Fruit(object):
  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)"%(cls,value)  
    cls.total = value
class Apple(Fruit):
  pass
class Orange(Fruit):
  Pass
app1 = Apple()
app1.set(200)
app2 = Apple()
org1 = Orange()
org1.set(300)
org2 = Orange()
app1.print_total()  #output 200
org1.print_total()  #output 300

删除上面代码中的注释语句后运行程序会得到以下结果:

calling class_method(<class '__main__.Apple'>,200)
calling class_method(<class '__main__.Orange'>,300)
200
12184820------>Fruit
类的类变量
12186364 ----->
动态生成的Apple
类的类变量
300
12184820------>Fruit
类的类变量
13810996 ----->
动态生成的Orange
类的类变量

简单分析可知,针对不同种类的水果对象调用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 "app1 is instance of Apple:"+str(isinstance(app1, Apple)) 
print "org1 is instance of Orange:"+str(isinstance(org1, Orange)) 

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

  @classmethod
  def Init_Product(cls,product_info):
    area, category, batch = map(int, product_info.split('-'))
    fruit = cls(area, category, batch)
    return fruit

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文