装饰器非常重要,不仅使代码看起来更简洁优雅,而且最大限度的复用了代码。本文主要讲解装饰器的3种用法,分别是:
1、闭包装饰器
2、类装饰器
3、Python第三方模块提供的方法
个人更推荐后面两种,可读性较好。这只是一个习惯而已,有人也觉得第一种更加简洁。
1. 闭包装饰器
闭包
闭包装饰器含有两个名词分别是闭包和装饰器,初学者可能不太清楚,在这里做一个简单的说明。闭包在很多语言中都有这个概念,是指外层函数的局部变量是内层函数的全局变量(概念比较狭义,方便理解Python中的闭包)
我们来看这样一个问题:
def add_tag(tag):
def add_content(content):
return "<%s> %s </%s>" % (tag, content, tag)
return add_content
>>>a = add_tag("a")
>>>a(content="hello world")
<a> hello world </a>
tag
这个变量对于外层函数add_tag
来说只是一个局部变量,但对与内层函数add_content
来说就是全局变量了,并且对封装函数中任何一个变量,外部都无法访问,这就形成了一个封闭的概念。
简单闭包装饰器
做一个输入检查的问题,验证name=admin,password=123456
。当然如果对于多个函数接口都需要验证这样的参数,使用装饰器是最好的选择。
def login(name, password):
if name != "admin" or password != "123456":
return "username or password error"
print "login success."
print "do something."
使用装饰器以后,登陆函数中间不必做验证,只需要实现逻辑:
def check_login(func):
def _wrapper(name, password):
if name != "admin" or password != "123456":
print "login failed"
return "username or password error"
return func(name, password)
return _wrapper
@check_login
def login(name, password):
print "login success."
print "do something."
>>>login("admin", "123456")
login success.
do something.
>>>login("admin", "156")
login failed
注意观察外层接收函数对象,内层接受参数,那么装饰就是这样的
>>>check_login(login)("admin", "123456")
login success.
do something.
>>>check_login(login)("admin", "1256")
login failed
发现结果是一样的,check_login(login)
返回的是内层函数对象,内层函数刚好接收用户名和密码两个参数,也就是说@
这个语法糖只是简化了展开式。
思考
仔细思考会发现,这里有问题,这个登陆检测装饰器只接受两个参数,如何支持任意多个参数呢,这个问题还是留给读者吧。
为了加深理解,再加一个日志装饰器,输出调用函数的名称,两个装饰器一起装饰login
函数。(此处有坑,小心)
def log(func):
def _wrapper(*args, **kwargs):
print "call %s " % func.__name__
return func(*args, **kwargs)
return _wrapper
def check_login(func):
def _wrapper(name, password):
if name != "admin" or password != "123456":
print "login failed"
return "username or password error"
return func(name, password)
return _wrapper
@log
@check_login
def login(name, password):
print "login success."
print "do something."
>>>login("admin", "156")
call _wrapper
login failed
>>>login("admin", "123456")
call _wrapper
login success.
do something.
装饰器的最外层函数接收的参数为函数名,这样函数名就作为内部的全局变量,在任何地方都可以调用了,内层函数接受函数的参数,主要的逻辑放在内层函数里面,比如添加日志信息,或者做参数检查等操作。最后就是按层级关系返回函数,但在这里还有一个问题,就是调用函数的名称发生了变化,因为外层函数是由_wrapper
返回的,因此最终得到的函数签名就变成了_wrapper
。
只需要导入functools
下的wraps
,然后装饰在内层函数上就能保证函数签名的了。
import functools
def log(func):
@functools.wraps(func)
def _wrapper(*args, **kwargs):
print "call %s " % func.__name__
return func(*args, **kwargs)
return _wrapper
给装饰器添加参数
不仅需要输出函数名的调用,还需要指定日志的级别,比如我们装饰一个函数@log(info)
,这个函数就得输出[info] call login
。只需要在最外层改变接受参数,次内层接受函数名,内层接受函数的参数,也就是加了三层,看起来比较复杂了。
import functools
def log(level):
def _decorator(func):
@functools.wraps(func)
def _wrapper(*args, **kwargs):
print "[%s] call %s " % (level, func.__name__)
return func(*args, **kwargs)
return _wrapper
return _decorator
@log("info")
def login(name, password):
print "login success."
print "do something."
>>>login("admin", "123456")
[info] call login
login success.
do something.
对于三层的装饰器,@
语法糖相当于log("info")(login)("admin", "123456")
,看着是不是有点蒙了,(我刚开始学习的时候也是抓狂的,所以推荐后面两种),理解以后用哪种就看个人口味了。给出部分代码如下:
def login(name, password):
print "login success."
print "do something."
>>>log("info")(login)("admin", "123456")
[info] call login
login success.
do something.
2. 类装饰器
最麻烦的闭包装饰器解决了,类装饰器写起来容易,并且可读性好多了,废话不多说,直接实现上面日志例子吧。
class Log(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print "call %s" % self.func.__name__
return self.func(*args, **kwargs)
@Log
def login(name, password):
print name, password
>>> login("admin", "123456")
call login
admin 123456
初始化返回的对象必须是可调用的,只需要改变类的__call__
属性,代码是不是易懂多了。实现带参数装饰器,也就是添加日志的级别的装饰器,稍稍麻烦一点:
class Log(object):
def __init__(self, level):
self.level = level
def __call__(self, func):
def _wrapper(*args, **kwargs):
print "[%s] call %s" % (self.level, func.__name__)
return func(*args, **kwargs)
return _wrapper
@Log("debug")
def login(name, password):
print name, password
>>> login("admin", "123456")
[debug] call login
admin 123456
3.第三方包实现的装饰器
wrapt
这个包封装了装饰器,直接使用@wrapt.decorator
装饰在函数上即可,对于无参数装饰器,实现很简单了,可读性就不用多说了,毕竟是第三方的包。
import wrapt
@wrapt.decorator
def log(func, instance, args, kwargs):
print "call %s" % func.__name__
return func(*args, **kwargs)
@log
def login(name, password):
print name, password
>>>login("admin", "123456")
call login
admin 123456
有参数的情况,还需要在外面加一层函数,这层函数用来接收参数。
import wrapt
def log(level):
@wrapt.decorator
def wrapper(func, instance, args, kwargs):
print "[%s]: call %s" % (level, func.__name__)
return func(*args, **kwargs)
return wrapper
@Log("debug")
def login(name, password):
print name, password
>>>login("admin", "123456")
[debug] call login
admin 123456
小结
装饰器非常重要,刚开始学习都是很困难的。当然如果你要绕过困难,不学也可以实现相同的功能,只是你的代码会很糟糕,可读性差,代码复用率低。Python就是简洁优雅的,你非要写出冗余代码,真心浪费了Guido的一片苦心,一个上午就写了这么点文章,后期还会继续完善。