2019-09-15
1.5k
Python函数式编程-装饰器
搬运自https://foofish.net/python-decorator.html
原文有若干处错误!
引入
装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
接下来,我们一步一步的来看装饰器是怎么形成和起作用的。
先来看一个简单函数:
1 2
| def foo(): print('I am foo')
|
现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:
1 2 3
| def foo(): print('I am foo') logging.info('foo is running')
|
假设现在其他函数也有类似的需求,如果再写一个logging在其他函数里面,这样就会造成大量重复代码。首先想到的解决办法肯定是重新定义一个函数专门来处理日志。
1 2 3 4 5 6 7 8
| def use_logging(func): logging.warn("%s is running" % func.__name__) func() def foo(): print('I am foo') use_logging(foo)
|
这样做逻辑上是没问题的,功能是实现了,但是我们调用的时候不再是调用真正的业务逻辑foo
函数,而是换成了use_logging
函数。那么有没有更好的方式的呢?当然有,答案就是装饰器。
简单装饰器
1 2 3 4 5 6 7 8 9 10 11 12
| import logging def use_logging(func): def wrapper(): logging.warning("%s is running" % func.__name__) return func() return wrapper
def foo(): print('I am foo')
foo = use_logging(foo) foo()
|
use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。
语法糖
@
符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import logging def use_logging(func): def wrapper(): logging.warning("%s is running" % func.__name__) return func() return wrapper
@use_logging def foo(): print('I am foo')
foo()
|
如上所示,有了 @ ,我们就可以省去foo = use_logging(foo)
这一句了,直接调用 foo() 即可得到想要的结果。
带参数的foo()
如果业务逻辑函数 foo 需要参数,例如:
1 2
| def foo(name): print('I am %s', name)
|
我们可以在定义 wrapper 函数的时候指定参数:
1 2 3
| def wrapper(name): logging.warning("%s is running" % func.__name__) return func(name)
|
这样 foo 函数定义的参数就可以定义在 wrapper 函数中。如果func
函数需要若干个参数的话,可以用*args
代替:
1 2 3
| def wrapper(*args): logging.warning("%s is running" % func.__name__) return func(*args)
|
如果 foo 函数还定义了一些关键字参数呢?比如:
1 2
| def foo(name, age=None, height=None): print('I am %s, age %s, height %s' % (name, age, height))
|
这时,你就可以把 wrapper 函数指定关键字函数:
1 2 3 4
| def wrapper(*args, **kw): logging.warn("%s is running" % func.__name__) return func(*args, **kw)
|
完整的函数定义和调用如下:
1 2 3 4 5 6 7 8 9 10 11 12
| import logging def use_logging(func): def wrapper(*args, **kw): logging.warn("%s is running" % func.__name__) return func(*args, **kw) return wrapper
@use_logging def foo(): print('I am foo')
foo()
|
带参数的装饰器
装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)
。这样,就为装饰器的编写和使用提供了更大的灵活性。比如:我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import logging
def use_logging(level): def decorator(func): def wrapper(*args, **kw): if level == 'warn': logging.warning("%s is running" % func.__name__) elif level == 'error': logging.error("%s is running" % func.__name__) return func(*args, **kw) return wrapper return decorator
@use_logging(level='error') def foo(name, age): print('I am %s, %s age' % (name, age))
foo('foo', 19)
|
上面的use_logging
是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。
具体而言,和两层嵌套的decorator相比,3层嵌套的效果是这样的:
1 2
| >>> foo = use_logging('error')(foo)
|
首先执行use_logging('error')
返回的是decorator
,再调用返回的函数,参数是foo
,即decorator(foo)
,返回的最终是wrapper
函数。
@use_logging(level="warn")
等价于@decorator
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring
、__name__
、参数列表,先看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
@log('execute') def now(): print('2015-3-25')
now() print(now.__name__)
|
输出结果如下:
1 2 3 4
| execute now(): 2015-3-25 'wrapper'
|
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
可以直接使用Python内置的functools.wraps
函数解决上述问题。一个完整的decorator如下:
1 2 3 4 5 6 7 8 9
| import functools
def log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
|
针对带参数的装饰器:
1 2 3 4 5 6 7 8 9 10 11
| import functools
def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
|