Python装饰器可谓是一个老生常谈的话题了,随便Google一下都有大量的Python装饰器教程,而且花样繁多,高档中档低档都有。本文的定位是面向会使用装饰器,了解闭包,但是时不时需要翻阅文档或教程的新手(比如像我这种),如果你是希望从这篇博客里面解锁一些使用装饰器的高级姿势,那么直接点击关闭网页即可。
@就是一个语法糖
先给大家补习一下基本功课,Python装饰器的使用是通过@符号来完成的,一个常见的装饰器例子如下:
@cache
def rpc_invoke():
pass
上述的例子等价于:
def rpc_invoke():
pass
rpc_invoke = cache(rpc_invoke)
因此,@符号其实是将下一行的函数作为参数传入@后面的装饰器函数(以函数作为参数的函数)中。
一层装饰器比较简单,但大家在工作也可能会经常碰到二层装饰器,还是以上述的cache decorator举例:
# 添加缓存时间
@cache(3600)
def rpc_inovke():
pass
等价于:
def rpc_inovke():
pass
rpc_invoke = (cache(3600))(rpc_invoke)
二层装饰器与一层装饰器的原理类似,只是在一层装饰器中,cache函数本身就是一个装饰器函数,而在二层装饰器中,调用cache函数会返回一个装饰器函数,即cache函数是一个装饰器函数的Generator。
从上述的例子中可以看出,其实@符号可有可无,只是为了程序员们编写方便而已,这种情况下@就是一个Syntactic Sugar。
编写装饰器的姿势
温习完使用装饰器的知识,下面来看下如何写装饰器。比如写一个最简单的一层cache装饰器,就起码需要两个def。
def cache(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = func.__name__
cache_result = cache.get(key)
if cache_result:
return cache_result
else:
result = func(*args, **kwargs)
cache.set(key, result)
return result
return wrapper
@cache
def rpc_invoke():
pass
最外面的一层def就是装饰器函数,当调用cache函数的时候,它返回wrapper函数用于替换原来的rpc_invoke函数。
最里面的一层def可以视为被装饰函数(在这里是rpc_invoke)的替身,即当调用rpc_invoke函数的时候,其实是在调用wrapper函数,而rpc_invoke函数作为wrapper函数的闭包变量在wrapper函数内部被调用,即func变量。
通俗点说,装饰的过程就是偷梁换柱,用wrapper函数取代rpc_invoke函数,而functools.wraps则是为了使这一个偷梁换柱更加逼真。
OK,那么是不是每一个装饰器都需要起码两个def呢?这倒不一定,具体取决于装饰器的用途,比如你是希望偷梁换柱,那基本就得需要两个def,但是如果只是通过装饰器收集信息呢?
下面介绍一下装饰器的另一种常用姿势,比如要收集程序中使用了某装饰器的所有函数:
functions = []
def decorator(func):
functions.append(func)
return func
@decorator
def func1():
pass
当启动程序的时候,func1就会自动被添加到functions变量中。
这个例子可能大家会觉得没啥卵用,事实上也的确没卵用,只是一个example。使用一层装饰器收集信息的场景比较少,但是如果使用二层装饰器就能玩出一些新花样了,比如Flask的路由设计:
class App(object):
ROUTE_MAP = {}
def route(url):
def decorator(func):
ROUTE_MAP[url] = func
return func
return decorator
@app.route('/user/info')
def get_user_info(user_id):
pass
通过装饰器收集每个路由对应的handler,并记录在app的一个字典中,那么当请求到达app的时候,app就能根据路由字典迅速找到handler并处理请求。
N层装饰器怎么办
一层装饰器好办,无非是要么一个def或者两个def,那要是写N层装饰器呢?
先不论这N层装饰器的合理性,这里提一个简单粗暴的计数方法:每增加一次参数传递,就加一层def。
以能够控制时间缓存的二层cache装饰器为例:
def cache(expiration=300)
def decorator(func):
@functools.wrap(func)
def wrapper(*args, **kwargs):
key = func.__name__
cache_result = cache.get(key)
if cache_result:
return cache_result
else:
result = func(*args, **kwargs)
cache.set(key, result)
return result
return wrapper
return decorator
有不同想法欢迎指出,祝大家周末快乐,不用加班~