修饰符和上下文管理器都会用额外的逻辑増强现有的代码,为代码包装额外的功能
- 修饰符主要考虑为现有函数增加额外的功能
- 上下文管理器更关注于确保你的代码在一个特定的上下文环境中执行、安排with语句之前、之后运行的代码。用修饰符也可以做类似的事情,不过如果你试图这么做,大多数 Python程序员都会认为你可能有点疯狂。
复制粘贴不是好主意
有时需要同时修改多个函数,为他们增加某个功能
- 直接在函数中复制粘贴代码:难以维护
- 解决方法1:把共享的代码放在一个函数中,再调用函数
这个策略可以解决维护问题(因为共享代码只放在一个地方,而不是到处复制粘贴)
不过,还是需要对多个函数做类似的修改,本质上同样还是复制粘贴!
另外,采用这种时,增加的代码会让原来函数要做的工作变得含糊。 - 解决方法2:使用函数修饰符
可以用额外的代码增强现有的函数,从而改变现有函数的行为,而不必修改其代码
另外,由于不会修改已有的函数代码,这样不会带来含糊:各个函数中的代码仍保持与其具体工作直接相关,新增的代码不会混杂其中
函数修饰符
函数修饰符允许你用额外的代码增强已有函数,而不需要修改已有函数的代码
- 函数修饰符本质上就是一个函数,其参数为函数,返回值为另一函数
向它传入被修饰函数作为参数,它对被修饰函数做相应处理后,返回另一个新函数(具体而言,它在内部定义了一个嵌套函数,并将其作为返回值) - 现有函数使用修饰符后,对这个现有函数的所有调用都会替换为调用[修饰符返回的函数]
- 因此,从修饰符返回的函数,应该与被修饰函数有同样的签名(参数个数和类型都相同)
签名:即函数def行的参数个数和类型
优点
- 逻辑抽象,分离各部分代码:保留函数本身的“本职工作”代码,单独编写额外的“包装代码”
- 用额外代码来增强一个现有函数:可以修改现有函数的行为而没有修改它的代码
如何创建函数修饰符
创建函数修饰符的要点:
- 一般把修饰符放在单独的模块中(单独创建一个.py新文件)从而能更容易地重用。但要保证在import修饰符时,解释器能够找到它:把. py文件与原文件放在同一工作目录下
- 函数修饰符就是一个函数:接收被修饰函数作为参数,并返回一个新函数
- 在修饰符在内部定义[用于返回的函数]
返回的新函数一般命名为wrapper
,因为它不仅调用了被修饰函数,还在周围用更多额外的代码来包装被修饰函数 - 返回的新函数
wrapper
与被修饰函数的签名相同,然而被修饰函数的参数个数可能不统一,保持“通用”的解决方案是:用*args
和**kwargs
作为wrapper
的参数 - 创建你自己的修饰符时,一定要导入并使用
functools
的wraps
函数
修饰一个函数时,函数可能忘记它的身份,其原因与函数如何向解释器标识自己的身份有关。为此,Python的标准库提供functools
模块来处理这些细节,其中的wraps
同样为一个修饰符,你只需要用wraps
修饰wrapper
函数(传入被修师函数作为wraps
的参数)即可。
通用模板
from functools import wraps
def decorator_name(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 1. Code to execute BEFORE calling the decorated function.
# 2. Call the decorated function as required, returning its
# results if needed.
return func(*args, **kwargs)
# 3. Code to execute INSTEAD of calling the decorated function.
return wrapper
对现有函数使用修饰符后,对现有函数的所有调用都会替换为调用[从修饰符返回的函数]
注意这里的签名(*args, **kwargs)
,这样能保证无论向[从修饰符返回的函数]传入了什么参数,都能原封不动的传入原来的函数
- 第一处
(*args, **kwargs)
指示函数wrapper能接收任意数量的参数/关键字参数 - wrapper内的
args
和kwargs
表示一个参数列表/参数字典 - 第二处
(*args, **kwargs)
表示将列表/字典展开为多个独立的参数,传入func
用例
需求:对于独立的三个Web页面,若用户已登录则提供相关页面,未登录则拒绝访问
创建函数修饰符(在单独的checker.py文件中)
from flask import session
from functools import wraps
def check_logged_in(func):
@wraps(func)#用wraps修饰wrapper函数
def wrapper(*args, **kwargs):
if 'logged_in' in session:
return func(*args, **kwargs)#正常提供页面内容
return 'You are NOT logged in.'#拒绝访问
return wrapper
使用函数修饰符:在被修饰函数的def
行之前,直接用@
引出函数修饰符即可
- 函数修饰符的第一个默认参数就是被修饰函数
这里如果没有其他要传入的参数,可以直接写成@check_logged_in
from checker import check_logged_in#导入函数修饰符
...
@app.route('/page1')#Flask中的函数修饰符
@check_logged_in#我们创建的函数修饰符
def page1():
return 'This is page 1.'
...