[关闭]
@kpatrick 2019-08-06T15:23:24.000000Z 字数 6464 阅读 80

python的闭包、装饰器和functools.wraps

work python

阅读资料
- 探究functools模块wraps装饰器的用途
- Why is “wrapper” is better name for a decorator than “adder”?
- 理解Python闭包概念
- Python学习之变量的作用域


1. 闭包

1.1 作用域

作用域的概念就是变量可以被感知的空间范围。

LEGB法则:
Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中def或lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,则会出发NameError错误。

引用这篇博客的资料:

Python中并不是所有的语句块中都会产生作用域。只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念。在作用域中定义的变量,一般只在作用域中有效。 需要注意的是:在if-elif-else、for-else、while、try-except\try-finally等关键字的语句块中并不会产成作用域。看下面的代码:

  1. # 1. 产生作用域
  2. def func():
  3. variable = 100
  4. print variable
  5. print variable
  6. # NameError: name 'variable' is not defined
  1. # 2. 不产生作用域
  2. if True:
  3. variable = 100
  4. print (variable)
  5. print ("******")
  6. print (variable)
  7. #100
  8. #******
  9. #100

1.2 闭包

闭包,个人理解是将一些变量打包进了函数中,然后这个函数生成之后供外部调用,打包进去的变量脱离了作用域的限制,和这个闭包函数共存。关于闭包的详情可以看这篇博客

闭包的概念:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

例:

  1. function lazy_sum(arr) {
  2. var sum = function () {
  3. return arr.reduce(function (x, y) {
  4. return x + y;
  5. });
  6. }
  7. return sum;
  8. }

执行情况:

  1. >>> var f = lazy_sum([1, 2, 3, 4, 5])
  2. >>> f()
  3. 15

1.3 闭包陷阱:

闭包创建后返回之时,其内部的自由变量是确定的值

有一个闭包陷阱的例子如下:

  1. def my_func(*args):
  2. fs = []
  3. for i in xrange(3):
  4. def func():
  5. return i * i
  6. fs.append(func)
  7. return fs
  8. fs1, fs2, fs3 = my_func()
  9. print fs1()
  10. print fs2()
  11. print fs3()

返回闭包列表fs之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数my_func返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。
当然这个内部函数引用的父函数中定义的变量也不是自由变量,而只是当前block中的一个local variable。

执行结果:

  1. 4
  2. 4
  3. 4

经过上面的分析,我们得出下面一个重要的经验:返回闭包中不要引用任何循环变量,或者后续会发生变化的变量。 这条规则本质上是在返回闭包前,闭包中引用的父函数中定义变量的值可能会发生不是我们期望的变化

正确的改写:

  1. def my_func(*args):
  2. fs = []
  3. for i in xrange(3):
  4. def func(_i = i): # 传给闭包的是一个具体值
  5. return _i * _i
  6. fs.append(func)
  7. return fs

2. 装饰器

何谓“装饰器”?
《A Byte of Python》中这样讲:
“Decorators are a shortcut to applying wrapper functions. This is helpful to “wrap” functionality with the same code over and over again.”

《Python参考手册(第4版)》6.5节描述如下:
“装饰器是一个函数,其主要用途是包装另一个函数或类。这种包装的首要目的是透明地修改或增强被包装对象的行为。”

Python官方文档中这样定义:
“A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().”

总的来说,装饰器(wrapper)是一个函数的工厂,他接收一个函数参数,返回的也是一个函数。一般其作用是将原有函数添加上新的一些功能,然后将新的功能更强的函数返回。 这里有一个打印函数执行时间的装饰器time_cost

  1. import time
  2. def time_cost(func):
  3. def wrapper(*args, **kwargs):
  4. s = time.time()
  5. func(*args, **kwargs)
  6. cost = (time.time() - s) * 1000
  7. print('time cost: {} ms'.format(int(cost)))
  8. return
  9. return wrapper

可以看出time_cost这个装饰器做了这么三件事:
1. 将传参func带进闭包函数wrapper中;
2. 闭包中将传递过来的func带上其原有参数*args**kwargs执行,然后用time模块统计其执行的时间;
3. 将闭包wrapper返回

2.1 语法糖@

被语法糖@装饰的函数,调用的时候等同于:

  1. wrapper(func)()

如果不用语法糖,也可以这么写:

  1. def func_wrapper(func):
  2. def wrapper(*args, **kwargs):
  3. # ···
  4. func(*args, **kwargs) # or res = func(*args, **kwargs)
  5. # ···
  6. retrun # return anything like func does
  7. def _func_name(*args, **kwargs):
  8. # ···
  9. return
  10. func_name = func_wrapper(_func_name) # func_name具有原函数_func_name的功能,而且还经过了func_wrapper装饰,因此它拥有更多的功能
  11. ···

2.2 带参数的装饰器

如果装饰器本身带有参数(如需判断逻辑or其他作用),则需要再包一层,例:

  1. # 装饰器本身带有参数
  2. def human(sex: str):
  3. def decorator(func):
  4. def wrapper():
  5. if sex == 'm':
  6. print('i am male')
  7. if sex == 'f':
  8. print('i am female')
  9. func()
  10. return
  11. return wrapper
  12. return decorator
  13. # 使用human装饰器
  14. @human(sex='m')
  15. def man():
  16. print('in function man()')
  17. @human(sex='f')
  18. def women():
  19. print('in function women()')

执行情况:

  1. >>> man()
  2. i am male
  3. in function man()
  4. >>> women()
  5. i am female
  6. in function women()

2.3 装饰带有参数的函数

其实在前面的例子中,已经有涉及了装饰带有参数的函数。实际上就是被装饰的函数本身是有自身参数的,为了使得wrapper通用,一般采取*args和**kwargs的方式传递参数,我们将前例改写一下:

  1. # 装饰器本身带有参数
  2. def human(sex: str):
  3. def decorator(func):
  4. def wrapper(*args, **kwargs): # 两层的闭包,传递进来sex和func
  5. if sex == 'm':
  6. print('i am male')
  7. if sex == 'f':
  8. print('i am female')
  9. func(*args, **kwargs)
  10. return
  11. return wrapper
  12. return decorator
  13. # 使用human装饰器
  14. @human(sex='m')
  15. def man(name):
  16. print('in function man(), my name is {}'.format(name))
  17. @human(sex='f')
  18. def women(name):
  19. print('in function women(), my name is {}'.format(name))

执行情况:

  1. >>> man('Tom')
  2. i am male
  3. in function man(), my name is Tom
  4. >>> women('Lucy')
  5. i am female
  6. in function women(), my name is Lucy

3. functools

关于functools里的wraps,个人理解的作用就是:装饰后返回的闭包,如果查看其__doc__, __name__等属性,获取到的是wrapper本身的值,用了functools.wraps装饰后,他能将这个返回的闭包的属性全体更新一遍,后面的人再次查看,看到的就是wrapped函数中的东西了。


分割线,下面全是阅读的文章的引用:


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注