闭包与装饰器
闭包与装饰器
主要内容:
- 闭包函数
- 装饰器介绍
- 简单装饰器
- 装饰器模板
- 装饰器语法糖
- 带参数的装饰器
- 多层装饰器
- wraps方法
1. 闭包函数
闭包函数,名字就很形象的表达了这类函数的特性。闭包函数是封闭的被包在外层函数内,在里面定义的一个函数,外面无法访问这种函数,只能通过外部函数返回闭包函数的函数名来访问它。
闭包函数可以访问外部函数的作用域里的变量名,也可以访问外部函数的参数,并且外部函数的作用域里的名字一经定义后,就无法在外面修改,这个特性,可以让我们传入一些参数到外部函数,并且固定住这些参数,让我们达到减少传参的数量的作用。下面是一个线性函数的例子。
def line(a, b):
def inner(x):
return a * x + b
return inner
line1 = line(2, 3)
print(line1(4)) # 只需要传入参数4就可以了
print(line1(5))
line2 = line(4, 4)
print(line2(6))
11 13 28
上面这个线性函数定义一次可以固定住斜率和截距,以后就可以传入自变量,就达到了线性函数的功能,想要修改线性函数只需要重新定义就可以了。这也是闭包函数的应用之一。接下来就是闭包函数最重要的作用,形成一个装饰器。
2. 装饰器介绍
在编程上,通常要满足一个开放封闭原则,开放是对外扩展开放,封闭是对内修改封闭,即当我们扩展功能时要不修改原来的代码,并且原先的调用方式也不能改变。装饰器就能很好的完成这项任务,并满足开放封闭原则。给装饰器下个定义就是装饰原先对象,并为之添加新功能的工具。
那怎么完成装饰器呢?其实很简单,原理在前面的闭包函数中已经展示了,我们可以把待装饰的函数当做参数传给闭包函数的外层函数,那么待装饰的函数的引用就会被保存,接下来我们可以在闭包函数(内层函数)里面调用这个待装饰函数,并且在前后可以加入自己需要的功能,最后在外层函数把闭包函数返回。这样我们调用外层函数,并把待装饰的函数传入,会得到闭包函数的引用,并且这个闭包函数同样具有待装饰函数的功能(因为在闭包函数内调用了),此时我们把闭包函数的引用重新赋值给待装饰函数的名字,那么就起到了装饰的功能。次后我们像原来一样调用原来的函数,但是实际已经添加了新功能了,但是调用者并不会发现实际上函数已经指向了一个新的函数地址。
3. 简单装饰器
实现一个统计函数运行时间的简单装饰器。
import time
def timer(func):
def inner():
s = time.time()
func()
e = time.time()
print('函数执行时间为%.5f秒' % (e-s))
return inner
def test():
time.sleep(0.5)
print('测试函数!!!')
test = timer(test)
test()
测试函数!!! 函数执行时间为0.50001秒
以上就是简单的装饰器函数,它实现了不改变接口而增添了运行时间检测的功能。但是上面这个装饰器函数有很大的缺陷,它不能装饰带关键字参数,多个未知参数的函数,也不能装饰有返回值的函数。那么就需要有一个万能的装饰器模板来完成这项功能。
4. 装饰器模板
所谓装饰器模板的第一个功能,能够接收可变长参数,这个实现就很简单了,装饰器函数的本质就是让待装饰函数指向闭包函数的内存地址,让闭包函数代替原来的函数,那么只要让闭包函数拥有和原函数一模一样的参数,就可以实现装饰带任意参数的函数了, 正好,python中的*args, **kwargs能够接收所有的位置参数和关键字参数,并且闭包函数调用原函数的时候,可以使用*和**的解包打散功能,把参数还原回去,这样就很完美了。
至于返回值的问题,只要在调用待装饰函数后用一个变量接收,最后闭包函数返回这个返回值就可以了。详细的如下面所示,
import time
def timer(func):
def inner(*args, **kwargs):
s = time.time()
res = func(*args, **kwargs)
e = time.time()
print('函数执行时间为%.5f秒' % (e-s))
return res
return inner
def test():
time.sleep(0.5)
print('测试函数!!!')
def test1(a):
time.sleep(0.6)
print(a)
print('测试函数1')
def test2():
time.sleep(0.7)
print('测试函数2')
return 666
test = timer(test)
test1 = timer(test1)
test2 = timer(test2)
test()
test1(888)
print(test2())
测试函数!!! 函数执行时间为0.50022秒 888 测试函数1 函数执行时间为0.60002秒 测试函数2 函数执行时间为0.70050秒 666
修改上面的测试函数运行时间的装饰器,它就能够适用于任何种类型的函数了。
5. 装饰器语法糖
所谓语法糖就是一种让人感到很甜很舒服的语法,Python总能够站在用户的角度,使得Python整体语法美观优雅。在上面的时间装饰器中,每次都要为待装饰器函数从新绑定新地址,且都需要在全局中写这几句话,这就使得这样的代码不美观,不协调,所以Python就为装饰器专门定义了一种语法,可以在待装饰函数的上面加@ + 装饰器函数就可以达到原先的功能。就像这样,简洁又优雅

6. 带参数的装饰器
当我们需要让内层函数(闭包函数)可以根据各种不同情况执行不同的功能,这就又涉及到至少一个新的参数了,那么如何去完成这个任务呢?按照装饰器模板函数来看,它的两层函数体的参数都已经是固定写法了,并不能修改,那么我们只能再在外面包第三层函数,在这个第三层函数里面加参数,这样我们就可以让最后的闭包函数使用到我们的参数了。
def outer(choice):
def wrapper(func):
def inner(*args, **kwargs):
if choice == 1:
print('选择1功能')
elif choice == 2:
print('选择2功能')
return func(*args, **kwargs)
return inner
return wrapper
@outer(1)
def test1():
print('这是test1')
@outer(2)
def test2():
print('这是test2')
test1()
test2()
选择1功能 这是test1 选择2功能 这是test2
7. 多层装饰器
装饰器本质就是返回一个新的函数地址,那么这个新的函数同样可以被其他函数装饰,因此装饰器可以多个相互叠加。
def f1(func):
print('执行f1')
def inner(*args, **kwargs):
print('调用f1里层函数')
return func(*args, **kwargs)
return inner
def f2(func):
print('执行f2')
def inner(*args, **kwargs):
print('调用f2里层函数')
return func(*args, **kwargs)
return inner
@f1
@f2
def test():
print('执行原函数')
执行f2 执行f1 调用f1里层函数 调用f2里层函数 执行原函数

多层装饰器可以理解为从下往上包装,从上往下拆包。即在装饰的时候一层一层往上装饰,当在外部正式调用的时候就从上一层一层往下解包。
8. wraps方法
装饰器装饰原函数后,返回的是一个新的函数地址,那么它虽然调用的函数名和原函数名一样,但是当打印后就会发现与原函数不一样,并且最重要的是原函数的文档也没有了,为了解决这些由装饰器引起的缺陷时,functools的wraps装饰器就能很好的解决这个问题。
def f(func):
@wraps(func)
def inner(*args, **kwargs):
print('调用f里层函数')
return func(*args, **kwargs)
return inner
@f
def test():
"""这是test"""
print(test.__doc__)
print(test.__name__)
这是test test
可以看到文档函数名问题都正常了。

浙公网安备 33010602011771号