装饰器或者闭包,在本质上都是对嵌套函数与作用域的运用。 装饰器用 @ 表示,装饰器可用于包装任何可调用的对象,并且可用于方法和函数。 实际项目中,主要是省去一些重复的、可复用的,或有依赖关系的代码。比如日志跟踪、夹具等注入或拦截。

具体参考文献 https://docs.python.org/3/whatsnew/2.4.html?highlight=decorator#pep-318-decorators-for-functions-and-methods https://www.python.org/dev/peps/pep-0318/ 《python foundation courses》version 3, chapter 6. (中文译《Python基础教程》第3版) 《fluent python》chapter 7

函数嵌套

函数嵌套的简单示例

  1. def foo():
  2. def bar():
  3. print("Hello, world!")
  4. bar()

这里分别定义了 foo() 函数,和一个嵌套在 foo() 的 bar() 函数。我们知道,所有的函数均是在运行时调用才会进入函数,也就是在上述代码中,只要调用 foo(),那么 bar() 将立刻被调用,最终使得 bar() 函数方法体被执行。
其代码执行过程是(可以在第4行打断点 debug 验证):1、4、2、3

闭包与装饰器

闭包

image.png

装饰器的实现与原理

https://wiki.python.org/moin/PythonDecoratorLibrary 或者可以看源码

装饰器就是闭包的实现案例。这里模拟实现一个简单的 @fixture

  1. import functools
  2. def fixture(func):
  3. def inner(*args, **kwargs): # **kwargs因为是接收字典
  4. print(func.__name__, "before.....")
  5. func()
  6. print(*args, **kwargs)
  7. print(func.__name__, "after......")
  8. return inner
  9. @fixture
  10. def hello(*args, **kwargs):
  11. print('实际函数执行的名字' + hello.__name__)
  12. if __name__ == '__main__':
  13. hello(1, 2, {'k1': 'v1'})
  14. # 本质上的调用方式即 fixture(hello()) 等价于 hello = fixture(hello)

A decorator is just a function that takes the function to be decorated as an argument and returns either the same function or some new object. The return value of the decorator need not be callable (though it typically is), unless further decorators will be applied to the result.

测试一下。将 命名空间 的函数属性名 inner 改成 函数调用 inner()
image.pngimage.png


多个装饰器(叠放装饰器)

指定了多个装饰器时,应用的顺序与列出的顺序相反。 在使用多个装饰器时,很容易出现不确定的结果。或者避免,或者经过测试

多个装饰器装饰的顺序是从里到外(就近原则),而调用的顺序是从外到里(就远原则)

官方示例如下

  1. @A
  2. @B
  3. @C
  4. def f ():
  5. ...
  6. # 上述方法相当于
  7. def f(): ...
  8. f = A(B(C(f)))

装饰器的作用

从上述可以看出装饰器的特性是

  • 能把被装饰的函数替换成其他函数
  • 装饰器在加载(导入)模块时立即执行

这意味着:可以用于元编程(在运行时改变程序的行为)

标准库中的装饰器

@functools.wraps()

在装饰器的实现示例中,其实有几个问题

  1. 遮盖了被装饰函数的 __name____doc__ 属性
  2. 不支持参数传递

因此,可以按需优化成

  1. import functools
  2. def fixture(func):
  3. @functools.wraps(func)
  4. def inner(*args, **kwargs): # **kwargs因为是接收字典
  5. print(func.__name__, "before.....")
  6. func()
  7. print(*args, **kwargs)
  8. print(func.__name__, "after......")
  9. return inner
  10. @fixture
  11. def hello(*args, **kwargs):
  12. print('实际函数执行的名字' + hello.__name__)
  13. if __name__ == '__main__':
  14. hello(1, {'k1': 'v1'})
  15. # 本质上的调用方式即 fixture(hello()) 等价于 hello = fixture(hello)
  1. 》》》 结果输出为
  2. hello before.....
  3. 实际函数执行的名字hello
  4. hello after......

@functools.lru_cache()

被该装饰器所装饰函数,它的所有参数必须是可散列的

@singledispatch

https://pypi.python.org/pypi/singledispatch https://www.python.org/dev/peps/pep-0443/

参数化装饰器

解析源码中的装饰器时,Python 把被装饰的函数作为第一个参数传给装饰器函数。那怎么让装饰器接受其他参数呢?答案是:创建一个装饰器 工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上

image.png
image.png

  1. import functools
  2. def fixture(scope='default'):
  3. def wrapper(func):
  4. @functools.wraps(func)
  5. def inner(*args, **kwargs):
  6. if scope == 'module':
  7. print('使用模块')
  8. elif scope == 'class':
  9. print('使用类')
  10. else:
  11. print("使用默认作用域")
  12. return
  13. return inner
  14. return wrapper
  15. @fixture(scope='module')
  16. def user(name: str, age: int):
  17. return user.name, user.age