闭包函数与装饰器

一、闭包函数

1、闭与包

函数被当做数据处理时,始终以自带的的作用域为准。若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该内嵌函数就是闭包函数

 1 x = 1
 2 def outer():
 3     x = 2
 4     def inner():
 5         print(x)
 6     return inner
 7 
 8 func = outer
 9 func()
10 # 输出结果为2

“闭”代表函数是内部的,“包”代表函数外包裹着对外层作用域的引用。闭包函数是函数嵌套、函数对象、名称空间与作用域结合体

2、特点:

  闭包函数必须在函数内部定义

  闭包函数可以引用外层函数的名字

二、装饰器

1、为何要用装饰器

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改

软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦出错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化层出不穷,必须为程序提供扩展的可能性,这就用到装饰器

2、装饰器作用

在不修改被装饰对象源代码与调用方式的前提下,添加新的功能

装饰器的定义必须遵循:

  不修改被装饰对象源代码

  不修改被装饰对象调用方式

3、装饰器的实现

为下面函数增加一个计时的功能

1 import time
2 def download_movie():
3     print('开始下载电影...')
4     # 模拟下载需要时间,等待3秒
5     time.sleep(3)
6     print('电影下载成功')

首先想到的可能是:

1 time_start = time.time()    # 统计开始时间
2 download_movie()      
3 time_end = time.time()      # 统计结束时间
4 print(time_end - time_start)

如果设计成遵循开放封闭原则,如下

 1 import time
 2 def download_movie():
 3     print('开始下载电影...')
 4     # 模拟下载需要时间,等待3秒
 5     time.sleep(3)
 6     print('电影下载成功')
 7 
 8 def time_record(func):
 9     def inner():
10         time_start = time.time()    # 统计开始时间
11         func()      # func() ---> download_movie()
12         time_end = time.time()      # 统计结束时间
13         print(f'总共下载时间{time_end - time_start}')  # 函数执行后获取执行时间
14     return inner
15 
16 download_movie = time_record(download_movie)
17 download_movie()    # download_movie() ---> download_movie()

这样我们便可以在不修改源代码和调用方式的前提下为其追加统计时间的功能

但是假如原函数有返回值,这种方法就不能满足要求,可以这样修改:

 1 import time
 2 def download_movie():
 3     print('开始下载电影...')
 4     # 模拟下载需要时间,等待3秒
 5     time.sleep(3)
 6     print('电影下载成功')
 7     return "zzz.mp4"
 8 
 9 
10 def time_record(func):
11     def inner():
12         time_start = time.time()    # 统计开始时间
13         res = func()      # func() ---> download_movie()
14         time_end = time.time()      # 统计结束时间
15         print(f'总共下载时间{time_end - time_start}')  # 函数执行后获取执行时间
16         return res
17     return inner
18 
19 
20 download_movie = time_record(download_movie)
21 download_movie()

现在又需要增加一种需求,假如原函数有参数传递,但是不知道有多少参数,可以这样修改:

 1 import time
 2 def download_movie(url):
 3     print(f'{url}开始下载电影...')
 4     # 模拟下载需要时间,等待3秒
 5     time.sleep(3)
 6     print(f'电影下载成功')
 7     return "zzz.mp4"
 8 
 9 
10 def time_record(func):
11     def inner(*args, **kwargs):
12         time_start = time.time()    # 统计开始时间
13         res = func(*args, **kwargs)      # func() ---> download_movie()
14         time_end = time.time()      # 统计结束时间
15         print(*args, f'总共下载时间{time_end - time_start}')  # 函数执行后获取执行时间
16         return res
17     return inner
18 
19 
20 download_movie = time_record(download_movie)
21 download_movie()

至此,我们便实现了一个装饰器的最终效果,它可以扩展原函数的功能,返回值和传递参数。模板可以表示为:

1 def wrapper(func):
2     def inner(*args, **kwargs):
3         # 修改原函数功能代码
4         res = func(*args, **kwargs)
5         return res
6     return inner
7 
8 func = warpper(func)
9 func()

4、装饰语法糖

为了简洁而优雅地使用装饰器,python提供了专门的装饰器语法来取代func = warpper(func)的形式,需要在被装饰对象的正上方加上@warpper,当解释器执行到@warper时,就会调用warpper函数,且把被装饰函数名当做参数传入

 1 import time
 2 def wrapper(func):
 3     def inner(*args, **kwargs):
 4         start_time = time.time()
 5         res = func(*args, **kwargs)
 6         end_time = time.time()
 7         print(end_time - start_time)
 8         return res
 9     return inner
10 @wrapper
11 def func():
12     time.sleep(3)
13 func()

 

上一篇:python安装scrapy库踩坑记


下一篇:JVM钩子函数的使用