Python @staticmethod 与 @classmethod 的区别分析

2.7k words

乍一看之下,@staticmethod 与 @classmethod 真的很相似,貌似都可以看作是静态函数,除了后者必须有一个传入参数外就貌似没区别了。但既然加入了,那也一定有开发者的考虑了。

相同点:调用者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TestClass():

@classmethod
def class_foo(cls):
print(cls)
print('test in class_foo')

@staticmethod
def static_foo():
print('test in static_foo')


if __name__ == '__main__':
test = TestClass()
test.class_foo()
test.static_foo()
TestClass.class_foo()
TestClass.static_foo()

输出:

1
2
3
4
5
6
<class '__main__.TestClass'>
test in class_foo
test in static_foo
<class '__main__.TestClass'>
test in class_foo
test in static_foo

无论是@classmethod还是@staticmethod 的调用者,即可以为一个已经是实例化的对象,也可以的是为一个类名,这一点上与 JAVA 或 C++ 的静态函数有点相似

主要区别:参数要求

@classmethod@staicmethod 多要求一个参数,而多处的这个参数 cls 指代的就是调用者的类。从上一个例子可以看出,cls实际上指代的就是调用者的类型,即test的类型为TestClass

应用举例

构造多个 constructor(构造函数)

对于 C++ 以及 JAVA 函数来说,由于存在重载机制,因此可以多个构造函数。比如在 JAVA 的 Test 类中,我可以声明一个用数组初始化的构造函数,也可以声明一个由字符串初始化的构造函数。但由于 Python 没有这种机制,貌似有点棘手。但可以用 @classmethod 弥补。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TestClass():
def __init__(self, lst):
self.lst = lst

@classmethod
def from_str(cls, s):
lst = s.split()
return cls(lst)

if __name__ == '__main__':
t = TestClass([1, 2, 3])
print(t.lst)
t = TestClass.from_str("1 2 3")
print(t.lst)

输出:

1
2
[1, 2, 3]
['1', '2', '3']

而如果用 @staticmethod 则显得无力了

构造工厂类

借助上面的特性,可以把在实际编写中写出一些简单的工厂类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Diagram():
@classmethod
def make_circle(Class, color):
return Class.Circle(color)

@classmethod
def make_square(Class, color):
return Class.Circle(color)

class Circle():
def __init__(self, color):
self.color = color

class Square():
def __init__(self, color):
self.color = color

class AnotherDiagram():
@classmethod
def make_circle(Class, color):
return Class.Circle(color)

@classmethod
def make_square(Class, color):
return Class.Circle(color)

class Circle():
def __init__(self, color):
self.color = color

class Square():
def __init__(self, color):
self.color = color

if __name__ == "__main__":
factory = Diagram
d_circle = factory.make_circle("red")
d_square = factory.make_square("green")

factory = AnotherDiagram
ad_circle = factory.make_circle("violet")
ad_square = factory.make_square("pink")
print(d_circle.color, d_square.color, ad_circle.color, ad_square.color)

例子中有两个工厂类:Diagram 和 AnotherDiagram 代表两种图表,两种图表都有 Circle 和Square 。另外值得注意的是,因为命名空间的原因,可以分别在两个工厂内声明 Circle 类和 Square 类。而不用大费力气的声明成这样:

1
2
3
4
5
6
7
8
9
10
11
class DiagramCirle():
pass

class DiagramSquare():
pass

class AnotherDiagramCircle():
pass

class AnotherDiagramSquare():
pass

运用 @classmethod 的工厂方式可读性显得更佳。而且在 factory 调用函数,不需要知道自己是 Diagram 或 AnotherDiagram,直接调用需要的绘图方法即可。

参考

  1. Python编程实战 运用设计模式、并发和程序库创建高质量程序 / Python in Practice: Create Better Programs Using Concurrency, Libraries, and Patterns
  2. Stack Overflow - Python @classmethod and @staticmethod for beginner?
  3. 知乎 - Python 中的 classmethod 和 staticmethod 有什么具体用途?