@可以使用一个函数(包装函数,wrapper function)替换另一个函数(被包装函数, wrapped function),例如:
def log(func):
def wrapper_fn():
print('log开始 ...')
func()
print('log结束 ...')
return wrapper_fn
#return "abc" #如果上一句替换成这一句,则最后的test()会返回一个错误:'str' object is not callable
#将wrapper 替换 wrapped, wrapper的返回就是wrapped的值
#这个例子中,由于log函数返回wrapper_fn(一个函数),test被换成wrapper_fn
#如果log函数返回一个字符串,test就是一个字符串了
@log #引用函数,称为the wrapper,
def test(): #被修饰函数 称为the wrapped
print('test ..')
test() #实际就是调用了wrapper_fn
print(test.__name__) #发现输出的是wrapper_fn
输出:
log开始 ...
test ..
log结束 ...
wrapper_fn
可见,test函数被替换了,但是其"__name__"属性也变了。
替换以后函数的一些属性改变了,如"__name__",可以使用functools.wraps函数来避免改变:
from functools import wraps
def log(func):
#使用functools.wraps可以使得wrapped 的name属性不变
@wraps(func, assigned=('__name__',), updated=('__dict__',)) #这里后两个参数需要加逗号,用于表示元组tuple, 否则成字符串了,就不对了。
def wrapper(*args,**kwargs):
print('log开始 ...',func.__name__)
ret = func(*args,**kwargs)
print('log结束 ...')
return ret
return wrapper
@log
def test1(s):
print('test1 ..', s)
return s
@log
def test2(s1, s2):
print('test2 ..', s1, s2)
return s1 + s2
test1('a')
test2('a','bc')
print(test1.__name__)
log开始 ... test1
test1 .. a
log结束 ...
log开始 ... test2
test2 .. a bc
log结束 ...
test1
可见"__name__"属性还是“test1”
当wrapper function 有参数的时候,需要进行两层封装:
from functools import wraps
def log(arg): #第一层,带一个参数,这个参数就是@的时候的参数
def _log(func): #第二层,带一个参数,这个参数是wrapped function
@wraps(func)
def wrapper(*args,**kwargs):
print('log开始 ...',func.__name__, arg)
ret = func(*args,**kwargs)
print('log结束 ...')
return ret
return wrapper
return _log
@log('module1') #如果wrapper 带有参数,则需要两层定义,如上面的log函数定义。
def test1(s):
print('test1 ..', s)
return s
@log('module1')
def test2(s1, s2):
print('test2 ..', s1, s2)
return s1 + s2
test1('a')
test2('a','bc')
输出:
log开始 ... test1 module1
test1 .. a
log结束 ...
log开始 ... test2 module1
test2 .. a bc
log结束 ...
总之,@叫做函数修饰符,也可以看做包装一下,用@后面的函数,包装紧接着定义的函数。
这样,以后调用被包装函数时,就替换成@后面的函数了。
这种技术可以使用在调试、测试、记录和额外事务处理中。
当不需要的时候,可直接注释掉@语句。