[要点抢先看]
1. 神奇的装饰器到底是什么
2. 装饰器的用法和语法糖
3. 装饰器如何添加参数
装饰器是 python 里的一个非常有意思的部分, 他用于封装函数代码, 显式的将封装器应用到被封装的函数上, 从而使得他们选择加入到装饰器指定的功能中. 对于在函数运行前处理常见前置条件(例如确认授权), 或在函数运行后确保清理(输出清除或异常处理), 装饰器都非常有用.
[妹子说] 听不明白, 太绕了!
简单来说, 装饰器就是实现了一个通用的功能, 然后将这个通用的功能应用到不同的, 需要使用这个功能的函数上, 从而避免每次都在不同函数上反复写相同的功能代码.
装饰器的本质是一个函数, 他接受被装饰的函数作为位置参数, 装饰器通过使用该参数来执行某些操作, 然后返回一个函数引用, 这个函数可以是原始函数, 或者是另外一个函数.
我们举例子说明, 装饰器是这样的函数, 他们接受被装饰的可调用函数作为唯一的参数, 并且返回一个可调用函数,
- registry = []
- def register(decorated):
- registry.append(decorated)
- return decorated
- def foo():
- return 3
- foo = register(foo)
- print(registry[0])
- <function foo at 0x00000000025D51E0>
复制代码
register 方法是一个简单的装饰器, 它把被装饰的函数添加到一个列表中, 然后这里是将未改变的被装饰函数返回, 可以看出, 装饰器一般是传入被装饰函数的引用, 然后经过一些指定的处理, 最后返回值也是一个函数引用.
还有一种更简单的语法形式:
装饰器的语法糖: 我们这里看到的对 foo 进行装饰的方法是运用
foo = register(foo)语句, 还有一种简单的用法是在声明函数的位置应用装饰器, 从而使得代码更容易阅读, 并且让人立刻意识到使用了装饰器
- registry = []
- def register(decorated):
- registry.append(decorated)
- return decorated
- @register
- def foo(x=3):
- return x
- @register
- def bar(x=5):
- return 5
- for func in registry:
- print(func())
- 3
- 5
复制代码
再看一个更复杂, 更一般化的装饰器函数. 装饰器的本质是在执行原有函数 (被装饰的函数) 的同时, 再加上一些额外的功能.
- def requires_ints(decorated):
- def inner(*args, **kwargs):
- kwarg_values = [i for i in kwargs.values()]
- for arg in list(args) + kwarg_values:
- if not isinstance(arg, int):
- raise TypeError('{} only accepts integers as arguments'.format(decorated.__name__))
- return decorated(*args, **kwargs)
- return inner
复制代码
在这个装饰器函数 requires_ints 我们可以看到, 他定义了一个内嵌函数 inner, 这个内嵌函数的参数首先收集被装饰函数的所有参数, 然后对其进行判断, 判断其是否为整数类型(这就是装饰器添加的额外功能), 然后再调用被装饰的函数本身, 最后将这个内嵌函数返回. 因此当我们再用原函数名进行调用的时候, 原来的被装饰函数的引用就能指向这个新的内嵌函数, 就能在实现原函数功能的基础上, 加上附加的功能了.
同时, 我们再提炼一下这里面的几个重难点:
第一, requires_ints 中, decorated 这个变量是内嵌作用域的变量, 在他调用退出后, 返回的 inner 函数是可以记住这个变量的.
第二, python 不支持函数的参数列表的多态, 即一个函数名只能对应唯一的参数列表形式.
第三, 在内嵌函数内部调用被装饰函数的时候, 使用了解包参数, 关于这 * args, **kwargs, 的参数形式, 前面章节中细讲过.
[妹子说] 那我们也用这个装饰器来装饰一个函数.
- @requires_ints
- def foo(x,y):
- print(x+y)
- foo(3,5)
- 8
复制代码
这里将名称 foo 赋给 inner 函数, 而不是赋给原来被定义的函数, 如果运行 foo(3,5), 将利用传入的这两个参数运行 inner 函数, inner 函数执行类型检查, 然后运行被装饰方法, 如果传入的不是整形数, 例如下面这个例子, 那么装饰器的附加功能就会进行类型检查:
- @requires_ints
- def foo(x,y):
- print(x+y)
- foo('a',5)
- Traceback (most recent call last):
- File "E:/12homework/12homework.py", line 15, in <module>
- foo('a',5)
- File "E:/12homework/12homework.py", line 7, in inner
- raise TypeError('{} only accepts integers as arguments'.format(decorated.__name__))
- TypeError: foo only accepts integers as arguments
复制代码
其次内嵌的函数和被装饰的函数的参数形式必须完全一样, 这里用的 * args, **kwargs 概况函数参数的一般形式, 因此也是完全对应的.
最后说说装饰器参数
最后来介绍这个复杂一些的话题, 装饰器参数. 之前我们列举的常规例子里, 装饰器只有一个参数, 就是被装饰的方法. 但是, 有时让装饰器自身带有一些需要的信息, 从而使装饰器可以用恰当的方式装饰方法十分有用.
这些参数并不是和被装饰的函数并列作为参数签名, 而是在原有装饰器的基础上额外再增加一层封装, 那么, 实质是这个接受其他参数的装饰器并不是一个实际的装饰器, 而是一个返回装饰器的函数.
最终返回的内嵌函数 inner 是最终使用 indent 和 sort_keys 参数的函数, 这没有问题
- import json
- def json_output(indent=None, sort_keys=False):
- def actual_decorator(decorated):
- def inner(*args, **kwargs):
- result = decorated(*args, **kwargs)
- return json.dumps(result, indent=indent, sort_keys=sort_keys)
- return inner
- return actual_decorator
- @json_output(indent=8)
- def do_nothing():
- return {'status':'done','func':'yes'}
- print(do_nothing())
- {
- "status": "done",
- "func": "yes"
- }
复制代码
我们在这里详细解释说明的是操作顺序, 看上去我们使用的是 @json_output(indent=8), 作这和之前的装饰器语法糖看上去有些不同, 实际上这个不是最终的装饰器函数, 通过调用 json_output(indent=8), 返回函数指针 actual_decorator, 这个函数才是真正放在 @后的装饰器函数, 原始的被装饰函数最终获得了内涵更丰富的 inner 函数对象, 完成了装饰过程, 值得一提的是, 所谓的装饰器参数最终传给了最内层的 inner 函数.
记住, 在定义装饰器函数后, 真正的装饰器函数只有一个参数, 那就是被装饰的函数指针, 而有其他参数的函数实质上只是装饰器的外围函数, 他可以依据参数对装饰器进行进一步的定制. 一句话: 一个函数不可能接受被装饰的方法, 又接受其他参数
在语法糖中 @func 这种不带括号的, 就是直接使用装饰器函数进行装饰, 如果是 @func()带括号的, 实质上是先调用 func()函数返回真正的装饰器, 然后再用 @进行调用.
[妹子说] 这个装饰器的功能可真的是在 python 里才见到, 真得好好学学呀!
来源: https://juejin.im/post/5b7d697051882542a82b900b