一,装饰器decorator
什么是装饰器?
假如我们定义了一个函数,又想运行时动态的增加功能,又不想改动原来的代码。应该怎么样做呢?
前面我们已经学习了高阶函数,高阶函数有以下特点:
可以接受函数作为参数
可以返回函数
那么我们是否可以接受一个函数,对其进行包装,返回一个新的函数呢?
定义一个新的函数new_fn(f) 接受一个函数作为参数,在其内部编写方法fn() 进行打印log,log为传入的函数的名字,并返回传入的函数的返回值,new_fn()则返回内部定义的函数fn()
def f1(x):
return x*x
def new_fn(f):
def fn(x):
print 'call'+f.__name__+'()'
return f(x)
return fn
f1=new_fn(f1)
print f1(2)
Python内置的@是为了简化装饰器的调用。
def new_fn(f):
def fn(x):
print 'call'+f.__name__+'()'
return f(x)
return fn
@new_fn
def f1(x):
return x*x
f1(3)
>>>
callf1()
>>>
常用的装饰器有:
打印日志:@log
检测性能:@performence
数据库事物:@transaction
URL路由:@post('/register')
编写无参数的decorator
Python的decorator本质上是一个高阶函数,它接受一个函数作为参数,返回一个新的函数
使用decorator用Pytho提供的@语法,这样可以避免手动编写f=decorate(f)这样的代码
@log为例:
def log(f):
def fn(x):
print 'call'+f.__name__+'()....'
return f(x)
return fn
@log
def factorial(n):
return reduce(lambda x,y:x*y,range(1,n+1))
print factorial(4)
对于其他参数不是一个的函数将会报错。因为log写死了返回一个参数的函数。可以通过Python的*args和**kw来保证两个参数都能正常调用。
def log(f):
def fn(*args,**kw):
print 'call'+f.__name__+'()....'
return f(*args,**kw)
return fn
@log
def factorial(n):
return reduce(lambda x,y:x*y,range(1,n+1))
@log
def fg(x,y):
return x+y
print factorial(4)
print fg(1,2)
编写带参数的decorator
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 factorial(n):
return reduce(lambda x,y:x*y,range(1,n+1))
@log('DEBUG')
def fg(x,y):
return x+y
print factorial(4)
print fg(1,2)
[DEBUG] factorial()...
24
[DEBUG] fg()...
3
完善decorator
@decorator可以动态实现函数功能的增加,但是,经过@decorator改造之后和原函数相比有没有其他的变化
例如:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print f2.__name__
函数的名字变成了wrapper可见,由于decorator返回的新函数函数名已经不是'f2',而是@log内部定义的'wrapper'。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@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
请思考带参数的@decorator,@functools.wraps应该放置在哪:
import time, functools
def performance(unit):
def perf_decorator(f):
@functools.wraps(f)
def wrapper(*args, **kw):
t1 = time.time()
r = f(*args, **kw)
t2 = time.time()
t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
print 'call %s() in %f %s' % (f.__name__, t, unit)
return r
return wrapper
return perf_decorator
@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial.__name__
偏函数:
例子:
print int('1234')
print int('1234',base=8)
print int('1234',base=16)
为了简化这种,我们定义一个int2()函数
def int2(x, base=2):
return int(x, base)
print int2('1000000')
functools.partial就可以帮助我们创建一个偏函数,不需要我们自己定义了
import functools
int2=functools.partial(int,base=2)
print int2('1000000')
>>>64
所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。