0. 标题
Python专家编程系列: 2. 装饰器介绍(Python Decorator)
读这个文章之前,先看一下我博客里面讲闭包的文章: Python闭包(Python Closures)介绍
作者: quantgalaxy@outlook.com
欢迎交流
1. What 什么是装饰器
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
装饰器的特点如下:
- 装饰器是闭包的一种应用,是返回值为函数的高阶函数;
- 装饰器修饰可调用对象,也可以带有参数和返回值;
- 装饰器中可以保持状态。
2. How 如何使用装饰器
2.1 简单的使用
举一个最简单的使用:
def outer(func):
def inner():
print('Before func()..')
func()
print('After func()..')
return inner
@outer
def hi():
print('Hi World')
# 装饰器的作用等同于: hi = outer(hi)
执行一下,输出是:
hi()
# 输出:
# Before func()..
# Hi World
# After func()..
通过上面的例子可以看出来,outer函数定义了一个闭包,返回了一个函数。
@outer的方式,是把下面的函数,当成参数,传给outer,实现了原有hi()的一个包装功能,
实际的作用是:hi = outer(hi)
2.2 带参数的使用
def outer(func):
def inner():
return func()
return inner
@outer
def haha(name):
return 'Haha ' + name
这样使用,会报错,因为传入的函数需要参数:
print(haha('Bob'))
>>> TypeError: inner() takes 0 positional arguments but 1 was given
你可以给 inner 函数加一个参数,但这样又不能适用无参数的函数了。
正确的解决方案是这样,利用Python 有 *args 和 **kwargs 可以接收任意数量的位置参数和关键字参数:
def outer(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@outer
def haha(name):
return 'Haha ' + name
@outer
def hehe():
return 'Hehe'
执行结果:
print(haha('Bob'))
# 输出:
# Haha Bob
print(hehe())
# 输出:
# Hehe
2.3 复杂一些的例子:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。
我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
>>> now()
call now():
2015-3-25
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
有的时候装饰器本身也需要接收参数,从而配置为不同的状态,比如打印日志时附带当前的text信息。
你要记得,不管怎么变化,装饰器必须返回一个函数。
既然这里的装饰器多了一对括号,那就是多了一层调用,
所以必须在之前无参数的情况下再增加一层的函数嵌套,也就是三层嵌套的函数:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的: >>> now = log(‘execute’)(now)
我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,
再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
2.4 获取函数名称
Python 具有强大的 自省能力, 即对象在运行时了解自身属性的能力。
比如,函数知道自己的名字:
def foo():
pass
print(foo.__name__)
# 输出:
# foo
但是如果用装饰器包装后,返回的名字就是内部函数的名字,因此函数的身份就变得混乱了:
def outer(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@outer
def my_func():
pass
print(my_func.__name__)
# 输出:
# inner
对于要记录原有函数的名字, Python 有内置的解决方案:
import functools
def outer(func):
@functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@outer
def my_func():
pass
print(my_func.__name__)
# 输出:
# my_func
甚至解决方案本身就是个 @wraps() 装饰器。
具体实现原理就不在这里解释了,可以具体查看functools.wraps的说明。
作者: quantgalaxy@outlook.com
欢迎交流
2.5 类作为装饰器
虽然前面例子里的装饰器都是函数,但是装饰器语法其实并不要求本身是函数,而只要是一个可调用对象即可。
既然如此,那我只要在类里实现了 call() 方法,岂不是类实例也可以做装饰器?
import functools
class Logit():
def __init__(self, name):
self.name = name
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
value = func(*args, **kwargs)
print(f'{self.name} is calling: ' + func.__name__)
return value
return wrapper
@Logit(name='Dusai')
def a_func():
pass
a_func()
# 输出:
# Dusai is calling: a_func
2.6 带状态的装饰器
结合闭包的原理,和装饰器配合使用,就可以实现状态的记录:
import functools
def counter(func):
count = 0
@functools.wraps(func)
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(count)
return func(*args, **kwargs)
return wrapper
@counter
def whatever():
pass
whatever()
whatever()
whatever()
# 输出:
# 1
# 2
# 3
通常闭包可以使用自由变量,但是不能修改其值。
因此这里用 nonlocal 表明 count 不是内层函数的局部变量,并优先在与闭包作用域最近的自由变量中寻找 count 变量。
2.7 类的装饰器
装饰器不仅可以作用于函数,同样也可以作用于类:
import functools
def logit(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('-' * 10)
print('Calling: ' + func.__name__)
value = func(*args, **kwargs)
print('-' * 10)
return value
return wrapper
@logit
class Tester():
def __init__(self):
print('__init__ ended')
def a_func(self):
print('a_func ended')
执行的时候,你会发现,装饰器,只在类实例化时候生效,后面类函数的调用,装饰器是不生效的:
tester = Tester()
tester.a_func()
# 输出
# ----------
# Calling: Tester
# __init__ ended
# ----------
# a_func ended
更多关于类装饰器的学习,参见: 像专家一样使用Python类装饰器(Python Class Decorators)
作者: quantgalaxy@outlook.com
欢迎交流
3. One More Thing
3.1 一个经典的装饰器模版
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 原函数运行前
# Do something
value = func(*args, **kwargs)
# 原函数运行后
# Do something
return value
return wrapper
3.2 一个计时的例子
import functools
import time
def time_it(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
#
value = func(*args, **kwargs)
#
end = time.perf_counter()
duration = end - start
print(f'Duration: {duration}')
return value
return wrapper
@time_it
def another_func():
time.sleep(1)
another_func()
# 输出:
# Duration: 1.004140400000324
4. 作者信息
作者: quantgalaxy@outlook.com
欢迎交流