classmethod和staticmethod

Python里面的类函数分为三种,一种是普通的方法,另外两种分别通过@classmethod和@staticmethod装饰器装饰,后面这两种看字面意思是类方法和静态方法,都可以直接用类名使用。一般场景中它们区别不大,大家平时也都随意使用,但在一些特定的场景中还是会有一些可以注意的地方。

代码编写

在代码编写上,如果在函数中继续使用类属性或函数,如下面的代码段,func1和func2都是用了A的a属性实现了相同的功能,在功能调用上没有什么区别,但是对于func1来说,A是直接作为硬编码写在调用处,比如我们修改了A为B,这时候代码里面对A的使用都需要修改成B,但是func2则不需要修改。所以这时候使用了classmethod会比较好.

1
2
3
4
5
6
7
8
9
10
11
class A(object):

a = 'test'

@staticmethod
def func1():
print A.a

@classmethod
def func2(cls):
print cls.a

继承场景

使用cls可以继承对实例的初始化,也可以调用当前类的相关实现逻辑。

在需要使用当前类的情况比较适用。如下面的例子中,继承以后就可以直接调用当前类实现的函数逻辑。在B调用func2的时候就是直接使用B构造了类实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A(object):

def __init__(self):
print 'test A'

@staticmethod
def func1():
return A()

@classmethod
def func2(cls):
return cls()

class B(A):

def __init__(self):
print 'test B'

if __name__ == '__main__':

A.func1()
A.func2()
B.func1()
B.func2()

输出结果:

1
2
3
4
test A
test A
test A
test B

这种方式对于一些抽象的实现比较常用,比如对于数据库操作来说,实际会对多个不同的数据库操作,比如对mysql和对redis是不一样的,不同的数据库都需要有自己的特殊的构造逻辑。如下面的实现,这种多态的使用在基类有多种不同派生的情况下比较常见。这里使用了一个DB基类,然后通过这个基类派生出各自的数据库连接类型,通过build构造不同的连接实例。

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
class DbBase(object):
def __init__(self):
self.db = self.connect()

@classmethod
def build(cls):
return cls()

@staticmethod
def connect():
pass

class MysqlDB(DbBase):

@staticmethod
def connect():
print 'mysql connect success'

class RedisDB(DbBase):

@staticmethod
def connect():
print 'redis connect success'

if __name__ == '__main__':

a = MysqlDB.build()
b = RedisDB.build()

输出结果

1
2
mysql connect success
redis connect success

代码结构

对于代码结构上来说有些时候我们需要在使用对象前先给对象一定的信息,或者我们使用对象前需要先获取对象的信息。对于某个外部依赖的服务,我们希望在使用进行初始化,比如Log服务,当然Log服务也可以是单例模式,在使用的时候进行初始化传入对应的参数也可以,不过一般像flask的web服务是在app启动的时候注册了对应的配置,而不是启动的时候就初始化了具体的实例,因为启动的时候并没有使用它。比如如下:

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
# coding: utf-8
import logging

class LogManager(object):
"""
日志管理类
"""
_logger = logging.getLogger()

@classmethod
def init(cls, config):
"""
初始化
"""
cls._logger = logging.getLogger(config.LOG_NAME)
cls._logger.setLevel(config.LOG_LEVEL)

cls._init_console(config)
cls._init_sentry(config)

@classmethod
def get_logger(cls):
"""
grab global logger
"""
return LogManager._logger

@staticmethod
def _init_console(config):
"""
初始化控制台输出
"""
pass

@staticmethod
def _init_sentry(config):
"""
初始化sentry输出
"""
pass

以上代码是一个很常用的日志管理类的模式,给定了初始化函数,在app初始化的时候进行配置注册,然后给了get_logger提供给其它地方获取log实例进行日志输出。下面给出具体使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Config(object):
"""
配置
"""
LOG_NAME = 'test'
LOG_LEVEL = logging.DEBUG

def init_app():
"""
初始化app
"""
LogManager.init(Config)
app = Flask(__name__, instance_relative_config=True)
return app

class TestService(object):
"""
测试类
"""
def __init__(self):
self.logger = LogManager.get_logger()

总结

通过以上例子可以看到classmethod其实有其比较特殊的一些性质,在实际开发中,根据需要去选择,而不是随意使用。

  1. 在有class初始化实例的情况下使用classmethod;
  2. 在需要使用类属性的情况下面尽量使用classmethod,以免将来继承;
  3. 在只是提供一些简单功能的情况下可以使用staticmethod。