函数装饰器

函数装饰器

装饰器基础知识

  • 动态的增加函数功能
  • 装饰器在被装饰的函数定义之后立即运行,函数装饰器在导入模块时立即执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def P(fun):
print('P')
def a(*args):
result = fun(*args)
print('a')
return result
return a
@P #此时执行装饰器,并且在被导入其他模块时,会立即执行
def b():
print('b')

@P
def c():
print('c')

print(1)
b()
print(1)
c()

结果为:

1
2
3
4
5
6
7
8
P
P
1
b
a
1
c
a
  • 装饰器通常在一个模块中定义,然后应用到其他模块中的函数上

  • 大多数的装饰器会在内部定义一个函数,然后将其返回。

  • 下面两种写法都可以
1
2
3
4
5
6
7
@decorate
def target():
pass

def target():
pass
target = decorate(target)

global

将变量作用域提升为全局

闭包

  • 闭包指延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量。
  • 只有涉及嵌套函数的时候才会有闭包问题(如:匿名函数),只有嵌套在其他函数中的函数才可能处理不在全局作用域中的外部变量

假如有个函数avg,它的作用是计算不断增加的值的均值,类似于:

1
2
3
4
5
6
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
12.0
  • 简单的实现方式为:
1
2
3
4
5
6
7
8
class Averager(object):
def _init_(self):
self.series = []

def _call_(sekf, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)
1
2
3
4
5
6
7
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
12.0
  • 使用高阶函数:
1
2
3
4
5
6
7
8
9
def make_averager():
series = []

def averager(new_lue):
series.append(new_value)
total = sum(self.series)
return total/len(series)

return averager
1
2
3
4
5
6
7
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
12.0

第一种不难看出,self.series是实例属性,调用call会不断的改变series的值,而第二种,series是make_averager函数的局部变量,所以当我们调用avg(10)的时,make_averager函数已经返回,故本地作用域一去不返。averager的闭包延伸到那个函数作用域之外,包含自由变量的series的绑定。

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

nonlocal

当被绑定的变量是数字或任何不可变类型(数字、字符串、元组)时,只能读取,不能更新,上面的series是列表,列表是可变对象。

python3故引入nonlocal声明,它的作用是将变量标记为自由变量。

实现一个简单的装饰器

一个简单的装饰器,输出函数运行时间,函数名字,函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# clockdeco_demo.py
import time

def clock(fun):
def clocked(*args):
t0 = time.perf_counter()
result = fun(*args) # 执行原函数,fun此时是闭包函数中的自由变量
t2 = time.perf_counter()
name = fun.__name__
arg_str = ','.join(repr(arg) for arg in args)
print('函数%s 运行了 %fs 参数:%s 结果:%s' % (name,(t2-t0),arg_str,result))
return result
return clocked
return clocked

使用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
from clockdeco import clockdeco_demo


@clockdeco_demo.clock
def sleeep(seconds):
time.sleep(seconds)


@clockdeco_demo.clock
def factorial(n):
return 1 if n < 2 else n * factorial(n - 1)


if __name__ == '__main__':
print('*' * 40, 'calling sleeep(0.5)')
sleeep(0.5)
print('*' * 40, 'calling factorial(6)')
print('6!= ', factorial(6))

运行结果

1
2
3
4
5
6
7
8
9
10
**************************************** calling sleeep(0.5)
函数sleeep 运行了 0.500877s 参数:0.5 结果:None
**************************************** calling factorial(6)
函数factorial 运行了 0.000001s 参数:1 结果:1
函数factorial 运行了 0.000011s 参数:2 结果:2
函数factorial 运行了 0.000018s 参数:3 结果:6
函数factorial 运行了 0.000024s 参数:4 结果:24
函数factorial 运行了 0.000031s 参数:5 结果:120
函数factorial 运行了 0.000042s 参数:6 结果:720
6!= 720

clocked大致做了下面几件事

  • 记录初试时间
  • 调用被装饰函数
  • 计算运行被装饰函数时间
  • 打印一系列时间,名字,参数,结果
  • 返回被装饰函数运行结果

标准库中的装饰器

python内置三个用于装饰方法的函数:

  • property
  • classmethod
  • staticmethod

另外常见的三个functools中的装饰器:

  • wraps 协助构建行为良好的装饰器,把func的相关属性复制到闭包函数中(上例的clocked),还能处理关键字参数
  • lru_cache 实现备忘功能,它把耗时的函数结果保存起来,避免传入相同的参数时重复计算。如实现 斐波那契数列
  • singledispatch 单分派泛函数,装饰器可以把整体方案拆分为多个模块,使用@singledispatch装饰的普通函数会变成泛函数:根据第一个参数的类型,以不同的方式执行相同操作的一组函数。

叠放装饰器

1
2
3
4
@d1
@d2
def f():
pass

等同于

1
2
3
4
def f():
pass

f = d1(d2(f))

参数化装饰器

让装饰器接受其他参数,先创建一个装饰器工厂函数,把参数传给它,这个工厂函数返回一个装饰器。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
registry = set()
def register(active=True):
def decorate(fun):
print("running register(active=%s)->decorate(%s)" % (active, fun))
if active:
registry.add(fun)
else:
registry.discard(fun)
return fun
return decorate

@register(active=False)
def f1():
print('running f1()')

@register()
def f2():
print('running f2()')

当active为True时,fun会被添加至registry中,当active为false中,会删除fun。