一文搞懂 python 中的 classmethod、staticmethod和普通的实例方法的使用场景
什么是类方法(classmethod)/静态方法(staticmethod)和普通成员方法?
首先看这样一个例子:
class A(object):
def m1(self, n):
# 属于实例对象,self 指代实例对象,
print("self:", self)
@classmethod
def m2(cls, n):
# 属于类对象A, cls指代类对象,就打印啥对象
print("cls", cls)
@staticmethod
def m3(n):
pass
a = A()
a.m1(1) # self: <__main__.A object at 0x7f78343e0290>
A.m2(1) # self: <class '__main__.A'>
A.m3(1)
需要搞清楚 类对象cls和实例对象self
三类方法(m1/m2/m3)和两类对象(A/a)的隶属关系
A.m1(A, 1) # self: <class '__main__.A'>
A.m1(a,1) # self: <__main__.A object at 0x7f78343e0290>
A.m1(int, 1) # self: <class 'int'>
a.m1(1) # self: <__main__.A object at 0x7f78343e0290>
小结:普通成员方法 (m1)隶属于类对象A和实例对象a, 常规用法是 a.m1()
A.m2(1) # cls: <class '__main__.A'>
a.m2(1) # cls: <class '__main__.A'>
小结: 类方法(m2) 隶属于类对象A, 实例对象a 因为是类对象A的实例化,所以通过 a 也能顺藤摸瓜地找到A,进而把cls 绑定到 类对象。
print(A.m3) # <function A.m3 at 0x7f2f7c1c8320>
print(a.m3) # <function A.m3 at 0x7f2f7c1c8320>
小结: 静态方法不和类对象A绑定,也不和实例对象a绑定,相当于普通方法,只不过是恰巧在类里面而已,通过类对象、实例对象都可以调用。
使用场景
静态方法 @staticmethod
class SignatureHandler(tornado.web.RequestHandler):
def get(self):
"""
根据签名判断请求是否来自微信
"""
signature = self.get_query_argument("signature", None)
echostr = self.get_query_argument("echostr", None)
timestamp = self.get_query_argument("timestamp", None)
nonce = self.get_query_argument("nonce", None)
if self._check_sign(TOKEN, timestamp, nonce, signature):
logger.info("微信签名校验成功")
self.write(echostr)
else:
self.write("你不是微信发过来的请求")
@staticmethod
def _check_sign(token, timestamp, nonce, signature):
sign = [token, timestamp, nonce]
sign.sort()
sign = "".join(sign)
sign = sha1(sign).hexdigest()
return sign == signature
_check_sign 中不需要调用 任何的类成员方法和成员属性,等价于普通函数放在了 类中。
小结: 如果方法 m3 的定义不需要调用 类的成员方法和成员变量,则 m3 可以 设计为 静态方法
疑问:静态方法的必要性?
静态方法和 实例对象无关, 和 类对象有关。比如
我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
类方法 @classmethod
场景一:模拟java 定义多个构造函数
python中 定义 class 时,只能有一个 初始化方法,不能按照不同情况初始化类。可以借助 class 方法来实现这个需求。
# coding:utf-8
class Book(object):
def __init__(self, title):
self.title = title
@classmethod
def create(cls, title):
book = cls(title=title)
return book
book1 = Book("python")
book2 = Book.create("python and django")
print(book1.title)
print(book2.title)
```py
#### 场景二:类A中某方法 mx()调用了静态方法 m3,则 mx最好定义成 类方法
```py
# coding:utf-8
class Foo(object):
X = 1
Y = 2
@staticmethod
def averag(*mixes):
return sum(mixes) / len(mixes)
@staticmethod
def static_method():
return Foo.averag(Foo.X, Foo.Y)
@classmethod
def class_method(cls):
return cls.averag(cls.X, cls.Y)
foo = Foo()
print(foo.static_method())
print(foo.class_method())
场景二中 推荐用 classmethod 而不是 staticmethod的原因:考虑继承的情况
# coding:utf-8
class Foo(object):
X = 1
Y = 2
@staticmethod
def averag(*mixes):
return sum(mixes) / len(mixes)
@staticmethod
def static_method():
return Foo.averag(Foo.X, Foo.Y)
@classmethod
def class_method(cls):
return cls.averag(cls.X, cls.Y)
class Son(Foo):
X = 3
Y = 5
@staticmethod
def averag(*mixes):
return sum(mixes) / 3
p = Son()
print(p.static_method())
print(p.class_method())
# 1.5
# 2.6666666666666665
小结: 条件:static_method 和 class_method 都有调用 静态方法
average. 结论:经子类Son继承后, 调用class_method, 其内部 调用的是子类Son的属性和方法;调用 static_method ,其内部依然调用的是父类的属性和方法;因为 static_method 调用静态方法时,指定了具体的类名(例子中是Foo, 而使用class_method 则是 cls)