一、闭包(Closure)

1.1 定义

  • 定义一:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
  • 定义二:闭包是由函数和与其相关的引用环境组合而成的实体。

显然这些定义有点抽象,从形式上说,如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量称为自由变量
_

1.2 实例

  1. def out_func(out_arg):
  2. def in_func(in_arg):
  3. return out_arg + in_arg
  4. return in_func
  5. add_10_counter = out_func(10)
  6. print(add_10_counter(5))
  7. print(add_10_counter(8))
  8. print(add_10_counter.__closure__)
  9. print(add_10_counter.__closure__[0].cell_contents)
  • add_10_counter = out_func(10),相当于初始化了一个out_arg=10的加法器
  • 内部函数in_func调用了外部函数命名空间中的out_arg,out_arg就是“自由变量”
  • in_func函数就是一个闭包(Closure)
  • outfunc函数调用结束后对应的内存空间被销毁,但是自由变量不会被销毁,自由变量被保存在了函数_closure属性中了

1.3 总结

Python中闭包“三要素”如下:

  1. 闭包函数必须被包裹在另一个函数中
  2. 闭包函数必须引用了上一级函数命名空间中的变量
  3. 外部函数必须返回了该闭包函数

二、装饰器(decorator)

2.1 定义

了解闭包是为了更好的理解装饰器,装饰器是Python语言中的高级语法,主要的功能是对一个函数、方法、或者类进行加工,作用是为已经存在的对象添加额外的功能,提升代码的可读性。主要用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。

2.2 实例

构造一个打印购物车商品的函数:

  1. def shopping_car(item=None):
  2. print('adding %s into shopping car' % item)
  3. shopping_car('apple')

为了保证用户的信息安全,我们需要对这种行为进行用户身份认证,如何组织代码?

2.2.1 Version One
  1. #增加验证功能
  2. def login(func):
  3. '''这里省略验证用户代码'''
  4. #打印验证成功消息
  5. print('authentication pass')
  6. #同时实现func函数功能
  7. func()
  8. def shopping_car(item=None):
  9. print('adding %s into shopping car'%item)
  10. #实现验证用户,然后打印购物车商品
  11. shopping_car = login(shopping_car)
  12. shopping_car('apple')

运行shopping_car('apple')时报错,原因很简单:login函数没有return语句,默认返回None类型。所以type(shopping_car)返回NoneType

2.2.2 Version Two
  1. #增加验证功能
  2. def login(func):
  3. '''这里省略验证用户代码'''
  4. #打印验证成功消息
  5. print('authentication pass')
  6. #同时实现func函数功能
  7. return func
  8. def shopping_car(item=None):
  9. print('adding %s into shopping car'%item)
  10. #实现验证用户,然后打印购物车商品
  11. shopping_car = login(shopping_car)
  12. shopping_car('apple')
  • 此时已经可以正常运行了
  • 但是,在运行shopping_car('apple')之前,就已经完成认证了。这类似于一打开购物网站就要求认证,而我们的需求是打印购物车商品时才要求身份认证
  • 同时,函数shopping_car依然可以独立运行,不受login函数的制约,等于没有认证

2.2.3 Version Three
  1. #增加验证功能
  2. def login(func):
  3. def inner_func(item):
  4. '''这里省略验证用户代码'''
  5. #打印验证成功消息
  6. print('authentication pass')
  7. #同时实现func函数功能
  8. func(item)
  9. return inner_func
  10. def shopping_car(item=None):
  11. print('adding %s into shopping car'%item)
  12. #实现验证用户,然后打印购物车商品
  13. shopping_car = login(shopping_car)
  14. shopping_car('apple')
  • 此时,才是我们要的结果,只有当shopping_car('apple')被调用时,login函数才真正被执行
  • 同时,inner_func就是闭包函数,自由变量是func函数

2.2.4 Version Four
  1. #增加验证功能
  2. def login(func):
  3. def inner_func(item):
  4. '''这里省略验证用户代码'''
  5. #打印验证成功消息
  6. print('authentication pass')
  7. #同时实现func函数功能
  8. return func(item)
  9. return inner_func
  10. def shopping_car(item=None):
  11. print('adding %s into shopping car'%item)
  12. return 'done'
  13. #实现验证用户,然后打印购物车商品
  14. shopping_car = login(shopping_car)
  15. res = shopping_car('apple')
  16. print(res)
  • 此时,shopping_car是一个有返回值的函数
  • login就是一个装饰器!
  1. # 语法糖
  2. @login
  3. def def shopping_car(item=None):
  4. print('adding %s into shopping car'%item)
  5. return 'done'
  • 可以直接调用这个装饰器,其作用等同于:shopping_car = login(shopping_car)

三、装饰器的解除

现在我们知道如何添加装饰器了,那么又如何来解除装饰器,调用原函数呢?

3.1 functools包中的wraps模块

要想解除装饰器,前提是使用了wraps模块,该模块的作用可以看成是备份原函数的各项属性,再通过调用wraps模块中的**__wrapped__**方法来解除装饰。如果没有使用wraps模块则会触发**AttributeError**

我们看个例子:

  1. # 不使用wraps模块
  2. def decorate_runtime(func):
  3. def wrapper():
  4. import time
  5. start = time.time()
  6. func()
  7. end = time.time()
  8. print("共睡眠 %f 秒" % (end - start))
  9. return wrapper
  10. @decorate_runtime
  11. def sleep_time():
  12. '''这是一个函数'''
  13. import random
  14. import math
  15. import time
  16. last = math.ceil(1 + random.random() * 10)
  17. time.sleep(last)
  18. print("awake!")
  19. help(sleep_time)
  20. sleep_time.__name__ # 此时,原函数的属性被覆盖了,无法正确调用
  • 你会发现此时,原函数sleeptime的属性被装饰器wrapper覆盖了,此时通过`sleeptime.__wrapped`解除装饰器会报错!为了解决这个问题,我们需要提前调用wraps模块,保存好原函数的属性:
  1. from functools import wraps # 插入wraps模块
  2. def decorate_runtime(func):
  3. @wraps(func) # 调用
  4. def wrapper():
  5. import time
  6. start = time.time()
  7. func()
  8. end = time.time()
  9. print("共睡眠 %f 秒" % (end - start))
  10. return wrapper
  11. @decorate_runtime
  12. def sleep_time():
  13. '''这是一个函数'''
  14. import random
  15. import math
  16. import time
  17. last = math.ceil(1 + random.random() * 10)
  18. time.sleep(last)
  19. print("awake!")
  20. original_sleep_time = sleep_time.__wrapped__
  21. original_sleep_time()

这样我们就可以顺利的解除装饰器,拿到原始函数了!