分清staticmethod和classmethod的使用场景

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

其中静态方法的用法如下:

class Pen(object):
    @staticmethod
    def write(arg1, arg2):
        pass

而类方法的用法如下:

class Toy(object):
    @classmethod
    def assembly(cls, arg1, arg2):
        pass

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

针对具体问题进行研究

假设有水果类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), id(cls.total))

    @classmethod
    def set(cls, value):
        print("calling class_method({0}, {1})".format(cls, value))
        cls.total = value


class Apple(Fruit):
    pass


class Orange(Fruit):
    pass


apple_one = Apple()
# calling class_method(<class '__main__.Apple'>, 1031)
apple_one.set(1031)

apple_two = Apple()

orange_one = Orange()
# calling class_method(<class '__main__.Orange'>, 2019)
orange_one.set(2019)

orange_two = Orange()

apple_one.print_total()
# 1031
# 1627286512 2582714044176

orange_one.print_total()
# 2019
# 1627286512 2582712725200

针对不同种类的水果对象调用set()方法的时候隐形传入的参数为该对象所对应的类,在调用set()的过程中动态生成了对应的类的类变量。这就是classmethod的妙处。

一个必须使用类方法而不是静态方法的例子

假设对于每一个Fruit类我们提供3个实例属性:area表示区域代码,category表示种类代码,batch表示批次号。现需要一个方法能够将以area-category-batch形式表示的字符串形式的输入转换为对应的属性并以对象返回。

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


class Apple(Fruit):
    pass


class Orange(Fruit):
    pass


apple = Apple('20', '1', '9')
print(isinstance(apple, Apple))     # True

orange = Orange.init_product("5-2-2")
print(isinstance(orange, Orange))   # False

运行程序会发现instance(orange, 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 <= 20
        assert 0 <= category <= 10
        assert 0 <= batch <= 72
    except AssertionError:
        return False
    return True

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

当然,如果有一组独立的方法,将其定义在一个模块中,通过模块来访问这些方法也是一个不错的选择。

(最近更新:2019年05月16日)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值