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(, 1031)
apple_one.set(1031)
apple_two = Apple()
orange_one = Orange()
# calling class_method(, 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日)