python语言里有【装饰器】这么个语法糖。简单地理解就是,它能够神不知鬼不觉地替换掉被装饰的函数。【李代桃僵】之类的形容词当然不是描述装饰器的,而似乎装饰器也很少在代码中使用。不过,最近hack别人的python代码(具体来说,就是hack强大的calibre软件的recipe处理,添加cache功能),发现装饰器的设计本意【处理函数的执行环境】十分的有用。
以django项目中的某个功能为例,网址http://py.banjuan.net/tool/build_recipe/对应django工程中的某个函数 tool.build_recipe(request),这个网址GET的时候吐出网页,而POST的处理结果以json方式返回。那么我们就可以编写两个装饰器来专门处理这种页面渲染和json转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from django.http import HttpResponse
from django.utils import simplejson
from django.shortcuts import render_to_response
def page_render(view):
def func(request, *arg):
(tpl, ret) = view(request, *arg)
return render_to_response(tpl, ret)
return func
def json_render(view):
def func(request, *arg):
pydata = view(request, *arg)
json = simplejson.dumps(pydata, ensure_ascii=False)
return HttpResponse(json)
return func
如果需要装饰,那么就是这样写:
1
2
3 @page_render
def build_recipe(request):
return "running build_recipe"
给略懂python额外讲解一下,上面的代码定义了两个函数page_render, json_render,函数的返回值还是函数。当这两个*_render(其实就是所谓的装饰器了,和普通函数没啥区别)用来装饰build_recipe(request)时,就会起到【李代桃僵】的左右,实际执行的就只是func(xxxx)了。被装饰的原函数build_recipe(request)会作为参数传递给page_render。形象地理解,上面那句@page_render会等效于:
1 build_recipe = page_render(build_recipe)
实际上,我的代码中是这样写的:
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 def build_recipe(request):
#@csrf_protect
@page_render
def get(request):
return ("tool/build_recipe.html", {})
@json_render
def post(request):
content = None
if 'content' in request.POST:
content = request.POST['content']
if 'recipe' in request.FILES:
f = request.FILES['recipe']
content = f.read()
ret = 1000
if content:
name = mktemp(prefix=u'ebook-', suffix='.recipe')
with open(name, 'wb') as f:
f.write(content)
f.close()
cmd = EBOOK_CONVERT%(name, name+u'.mobi')
#ret = os.system(cmd)
ret = cmd
return {'ret': ret}
if request.method == 'POST':
return post(request)
else:
return get(request)
根据request.method来分流处理,get()/post()只是返回基本数据,然后交由各自的装饰器进行数据渲染(弄成网页,变为json等)。于是上面的page_render/json_render便可以大量应用与其他类型的函数上,节省代码。
而在实际项目中,除了页面渲染,一个网页请求可能需要先验证登陆态、然后校验典型参数的有效性,为了调试还得统一捕抓异常,为了对付各种黑客还需要csrf_token(就是上面那行注释掉的装饰器),返回结果也还要统一,等等。这些公共的操作都是可以使用装饰器来实现。根据不同的页面权限,组合使用不同的装饰器(会从上倒下层层装饰),达到简化工作的目的。
当然,装饰器的另外一个作用技巧是用来实现注册功能,能够【显得】简约、整洁许多:
1
2
3
4
5
6
7
8
9
10
11
12 funcs = {}
def register(func):
funcs[func.func_name] = func
return func
@register
def abc(msg): print msg
@register
def show(): print "hello"
print len(funcs), " functions registed"
最后,果然觉得python的诡异细节真是无穷尽啊。
啊,补充说一下。最初想要用装饰器,是因为calibre的处理recipre的代码中,下载url的start_fetch(url)被调用得太过凌乱了,手动修改各处调用来增加cache会很崩溃的。于是就想到了【李代桃僵】的装饰器的特性,虽然其他地方仍然是调用start_fetch(url),但是已经被我神不知鬼不觉地掉包了,HACK成功!或许有人会想,为啥不直接修改start_fetch(url)的内部代码??介个嘛,这要是因为start_fetch(url)内处的处理也很冗长凌乱,改起来也很崩溃……所以,还是【装饰器】好用啊!