15:装饰器
-
装饰器介绍
-
装饰器的实现
内容详细:
1 装饰器介绍
1.1 为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的
# 对扩展开放:意味着有新的需求或变化时,可以对现有的代码进行扩展,以适应新的情况
# 对修改封闭:意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改
软件包含的所有的功能的源代码以及调用方式,都应该避免修改,否则一旦改错,极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器
1.2 什么是装饰器
'装饰':代指为被装饰对象添加新的功能
'器':代指器具/工具
装饰器与被装饰的对象均可以是任意可调用对象
# 概括的讲,装饰器的作用就是:在不修改被装饰对象源代码和调用方式的前提下 ---> 为被装饰对象添加额外的功能
装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景
装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用
ps:可调用对象有函数、方法或者类
2 装饰器的实现
函数装饰器分为:'无参装饰器'和'有参装饰器'两种,二者的实现原理一样,都是'函数嵌套+闭包+函数对象'的组合使用的产物
2.1 无参装饰器的实现
如果想为下述函数添加统计其执行时间的功能
import time def index(): time.sleep(3) print('Welcome to the index page’) return 200 index() #函数执行
遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样:
strat_time = time.time() index() # 函数执行 stop_time = time.time() print('run time is %s' %(stop_time-start_time)) 考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入 def wrapper(func): # 通过参数接收外部的值 start_time=time.time() res=func() stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res 但之后函数的调用方式都需要统一改成: wrapper(index) wrapper(其他函数)
========================================================================================
但这就违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下:
def timer(func): def wrapper(): # 引用外部作用域的变量func start_time=time.time() res=func() stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res return wrapper
这样我们便可以在不修改被装饰函数源代码和调用方式的前提下,为其添加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名/函数名index 如下:
index = timer(index) # 得到index = wrapper,wrapper携带对外作用域的引用:func = 原始的index index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
========================================================================================
至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能
但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常
def home(name): time.sleep(5) print('Welcome to the home page',name) home=timer(home) home('egon') #抛出异常 TypeError: wrapper() takes 0 positional arguments but 1 was given
========================================================================================
之所以会抛出异常,是因为home('egon')调用的其实是wrapper('egon'),而函数wrapper没有参数
wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况
要使用:*args + **kwargs的组合,于是 ---> 最终版装饰器timer如下:
def timer(func): def wrapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res return wrapper