背景知识
:
1.高阶函数:
函数可以作为另一个函数的参数、返回值。
2.闭包:
def g():
print 'g()...'
def f():
print 'f()...'
return g
将 g 的定义移入函数 f 内部,
防止其他代码调用 g:
def f():
print 'f()...'
def g():
print 'g()...'
return g
def f(lst):
def g():
return sum(lst)
return g
注意: 发现没法把 g 移到 f 的外部,因为它引用了 f 的参数 lst。
像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。
问题:
已经有了一个(些)函数,想在不改动原函数的情况下,增加新的功能。
如:
想在执行函数的时候,增加打印日志的功能,但是不修改原函数内的代码。
def f1(x): def f2(x):
return x*2 return x*3
思路:
高阶函数可以把 函数 作为参数,也可以返回一个函数。
那么,是否可以使用高阶函数,接受一个原函数,对其包装,然后返回一个新函数?
def f1(x): //原函数
return x*2
def new_fn(f): //装饰器函数
def fn(x):
print 'call' + f.__name__
return f(x)
return fn
调用装饰器函数:
g1 = new_fn(f1)
g1(5)
f1 = new_fn(f1)
f1(5)
@new_fn
def f1(x):
return x*2
等价于
def f1(x):
return x*2
f1 = new_fn(f1)
装饰器的作用:
避免每个函数编写重复性代码。
打印日志:@log
检测性能:@performance
数据库事务:transition
URL路由:@post('/register')
无参数的装饰器
def log(f):
def fn(*args, **kw):
print 'call ' + f.__name__
return f(*args, **kw)
return fn
log打印的语句是不能变的(除了函数名)
带参数参数的装饰器
需求:
希望有的打印出'[INFO] call xxx()...',
有的打印出'[DEBUG] call xxx()...',
这时,log函数本身就需要传入'INFO'或'DEBUG'这样的参数,类似这样:
@log('DEBUG')
def my_func():
pass
把上面的定义翻译成高阶函数的调用,就是:
my_func = log('DEBUG')(my_func)
再展开一下:
log_decorator = log('DEBUG')
my_func = log_decorator(my_func)
上面的语句又相当于:
log_decorator = log('DEBUG')
@log_decorator
def my_func():
pass
实现:
所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:
def log(prefix):
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
@log('DEBUG')
def test():
pass
print test()
执行结果:
[DEBUG] test()...
None
对于这种3层嵌套的decorator定义,你可以先把它拆开:
# 标准decorator:
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
# 返回decorator:
def log(prefix):
return log_decorator(f)
拆开以后会发现,调用会失败,因为在3层嵌套的decorator定义中,最内层的wrapper引用了最外层的参数prefix,所以,把一个闭包拆成普通的函数调用会比较困难。不支持闭包的编程语言要实现同样的功能就需要更多的代码。
完善装饰器
如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__
return wrapper
这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
最后需要指出,由于我们把原函数签名改成了(*args, **kw),因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数:
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper也可能改变原函数的参数名,因为新函数的参数名始终是 'x',原函数定义的参数名不一定叫 'x'。