Python里我们经常能见到@开头的句法,也就是人们常说的装饰器(decorator)。装饰器是Python非常重要的一部分,能够产出更易于维护的代码。这篇文章会给大家带来装饰器的介绍以及几个实用的例子。
装饰器是啥
假设我有一个func_foo函数如下。
def func_foo():
print("运行func_foo")
如果我们每次都要在函数前后插入日志,最简单的办法是把代码改成如下。
def func_foo():
print("在装饰器前我要做这些")
print("运行func_foo")
print("装饰器后做这些")
但是如果我们想在func_bar,func_xxx等函数中一概插入同样的日志呢,一个个函数去改不仅费时,以后也难以维护,于是会想到以下办法。
def a_decorator(a_func):
def wrap_the_func():
print("在装饰器前我要做这些")
a_func()
print("装饰器后做这些")
return wrap_the_func
def func_foo():
print("运行func_foo")
def func_bar():
print("运行func_bar")
func_foo = a_decorator(func_foo) # func1
func_bar = a_decorator(func_bar) # func2
在上面这个例子里,如果我们需要改变插入日志的内容,我们只需要改变wrap_the_func里的内容即可。但是美中不足的是我们需要添加func1以及func2额外两行代码,那么有没有办法让我们能够更简洁更Pythonic地表达我们在最后这两行中所要表达的东西呢?
当然有!那不正是装饰器嘛!在Python里,上面的例子可以写成。
@a_decorator
def func_foo():
print("运行func_foo")
@a_decorator
def func_bar():
print("运行func_bar")
简单吧,@a_decorator加到func_foo的效果是和func_foo = a_decorator(func_foo)一样的,这就是装饰器的强大之处。装饰器可以在不修改函数或者类本身代码的情况下给函数或者类增加额外的功能以及改变他们的用法。
学习Python中的小伙伴,需要学习资料的话,可以到我的微信公众号:Python学习知识圈,后台回复:“01”,即可拿Python学习资料
这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。送给正在学习python的小伙伴!这里是python学习者聚集地,欢迎初学和进阶中的小伙伴!
了解装饰器基本概念后,接下来给大家带来几个Python装饰器在实战中的几个例子。
1)权限认证
在做服务器端的时候我们通常得检测一个用户是否有权限进行某些操作,因此在某些web handlers里可能会加上额外的逻辑来检查一下用户权限,比如下面这个WebHandler里,我们可以在每次函数开头通过调用写好的PlatformAuthenticated方法来认证一下用户。
def PlatformAuthenticated(auth, auth_level):
if not auth or not check_auth(auth.username, auth.password):
raise Exception("BlahBlahBlah")
class WebHandler:
def get(self, request, response):
auth = request.authorization(ADMIN)
PlatformAuthenticated(auth, ADMIN)
do_something()
但是这样写的问题在于一旦handlers多了,亦或者是权限分成好几种级别的话,代码的维护性以及可读性都会大大下降,因为我们需要把这些额外的代码加入到每个handler的get方法里边。这时候可以把PlatformAuthenticated变成一个装饰器,再把这个装饰器添加到相应的handlers里边。比如
def PlatformAuthenticated(auth_level):
class StrictPlatformAuth:
def init(self, handler):
self.handler = handler
def __call__(self, request, response):
auth = request.authorization(auth_level)
if not auth or not check_auth(auth.username, auth.password):
raise Exception("BlahBlahBlah")
self.handler(request, response)
return StrictPlatformAuth
class WebHandler:
@PlatformAuthenticated(ADMIN)
def get(self, request, response):
do_something()
class RobotWebHandler:
@PlatformAuthenticated(ROBOT)
def get(self, request, response):
do_something()
上面这个装饰器相当于把get这个方法变成了
get = PlatformAuthenticated(auth_level)(get)
这样一来,通过装饰器,我们每次调用get也变成了调用PlatformAuthenticated里的call (不熟悉call的朋友可以百度/google之),这样在不需要改变每个handlers代码本身的情况下就可以进行不同层级的权限认证了。
2)载入缓存
在编写应用程序的时候,我们经常会向数据库或者数据仓库发送请求读取某块数据,而由于从数据库中读取数据非常费时,我们会把读过的数据载入到redis等基于内存的数据库里。在Python里我们也可以把这一块载入缓存的逻辑写到一个装饰器里,让有需求的函数调用这个装饰器。
下面我们就写一个这样的基本款装饰器,这个装饰器会以函数的参数作为key,返回值作为value来存入缓存里。
def cached(expires_secs):
def wrapper(func):
key = f"redis_decorator:{func.module}:{func.name}"
def execute(*args):
for arg in args:
key += str(arg)
value = redis.get(key)
if not value:
redis.set(key, value, expires_secs)
value = func(*args)
return value
return execute
return wrapper
@cached(86400)
def run_query(query_type, date):
return execute_query(query_type, date)
因为有了cached这个装饰器,每次在跑run_query之前我们都会先检查之前是否执行过同样的查询,如果执行过得话我们理论上会在redis里找到查询的返回值,这样就不需要再从数据库里读取数据了。如果没有执行过得话我们则会从数据库里把数据读取下来,然后再把数据载入到redis里。
- 插入日志
在编程中把重要的信息log出来不仅能够帮助自己调试,也有利于提高代码的可维护性。假如我们要在每个Python函数里把某些通用的信息都加入到日志当中,利用装饰器可以省去很多的功夫。比如把下面这个logger装饰器加入到函数中后,每次函数执行前都会先把函数的名字给log出来。
def logger(func):
@wraps(func)
def log(*args, *kwargs):
logging.info(func.name + " 被调用了")
return func(args, **kwargs)
return log
@logger
def bar():
pass
也可以把log保存在不同文件上
def logger(path):
def log_decorator(func):
@wraps(func)
def log(*args, *kwargs):
with open(path, 'a') as writer:
writer.write(func.name + " 被调用了")
return func(args, **kwargs)
return log
return log_decorator
@logger("/var/log/bar.txt")
def bar():
pass
总结
Python装饰器在写自己的库的时候真的是必不可少的利器,其他常见的应用还有单元测试,安排协程序(coroutine),单例模式等等,以后有空再补上更多的例子。