什么是装饰器
从字面意义来理解“装饰器”这三个字,器指的就是函数,所以装饰器本质是一个函数,功能是为其他函数添加附加功能,举个简单的例子,一段程序你想为其增加一段统计运行时间的功能
原则:
1.不修改被装饰的函数的源代码
2.不修改被装饰的函数的调用
如何实现一个装饰器
装饰器=高阶函数+函数嵌套+闭包,要想实现一个装饰器,上述的知识储备不能少,现在就来讲解高阶函数+函数嵌套+闭包的基本知识,为学习装饰器做铺垫
(1)高阶函数
什么是高阶函数呢?其定义如下
- 函数接受的参数是一个函数名
- 函数返回的参数是一个函数名
- 满足上述任意一个条件的函数就是高阶函数
def foo(): #定义一个普通函数 print("hello CodeScrew") def test1(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 print(Func) def test2(): #定义一个高阶函数,这个函数的特征是返回值是一个函数名 def foo2(): print("hello") return foo2 test1(foo) test2()
以上函数test1和test2都是高阶函数,有了这个概念我们似乎有了想法,现在我要给foo()这个函数增加打印其运行时间的做法可以实现如下
import time def foo(): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("hello CodeScrew") def test(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 start_time = time.time() Func() end_time = time.time() print(end_time - start_time) test(foo)
利用了高阶函数的第一个性质:函数接受的参数是一个函数名,写出了以上代码。我们好像是给foo()这个函数加上了统计运行时间的功能,而且没有修改foo()函数的源代码,但是问题来了,但是这里修改了foo()这个函数的调用。这显然还不满足最终的需求
那高阶函数的第二个性质:函数的返回值是一个函数名,对我们又有什么作用呢?我们可以写出一份示例代码
import time def foo(): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("hello CodeScrew") def test(): #定义一个高阶函数,这里是利用第二个性质,即返回值是一个函数名 return foo foo = test() foo()
以上函数的牛逼之处在于,调用还是使用了foo(),没有修改foo()的调用方式
当高阶函数的两个性质一起使用,我们得到了以下代码,目的还是为代码增加统计时间功能:
import time def foo(): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("hello CodeScrew") def test(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 start_time = time.time() Func() end_time = time.time() print(end_time - start_time) return Func foo = test(foo) foo() #打印结果为 # hello CodeScrew # 0.5004322528839111 # hello CodeScrew
上述代码已经实现不改变源代码,不改变调用,为函数增加统计运行时间功能,但是出现了新的问题,函数被运行了两遍,如何解决这个问题呢,我们现在学习以下函数嵌套+闭包的知识,两个一起讲
(2)函数嵌套
(3)闭包
什么叫函数嵌套呢,函数嵌套指的是函数定义里面还存在着函数定义,
什么叫闭包呢,闭包实际上就是对变量的作用域的另一种说法
用代码来讲解,下面的代码有函数嵌套,father里面定义了个son,这就是函数嵌套。
这里面有三个包,包里面的东西就是变量(函数也是变量),比如son这个包里存了2个东西,name和granson。print这个不算是变量。
这里有个注意点就是假如3号包中name= "王五"这一行被屏蔽,那么grandson这个print中的name就要去他的上一级包2号包中找,得到name="李四"
好了,我们怎么利用以上的特性呢,我们已经可以实现装饰器的基本框架了,如下代码所示:
import time def foo(): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("hello CodeScrew") def test(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 def wrapper(): #函数嵌套 start_time = time.time() Func() end_time = time.time() print(end_time -start_time) return wrapper #返回嵌套的函数 foo = test(foo) foo()
上述代码已经可以实现不改变源代码,不改变调用的情况下为函数增加了新功能,也解决了函数被调用两次的问题,但是还存在一点小瑕疵,中间有个foo = test(foo)的操作
对于这个瑕疵,python为我们提供了语法糖,使用@装饰器 放置在要被装饰的函数定义前面即可,如下代码所示
import time def test(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 def wrapper(): #函数嵌套 start_time = time.time() Func() end_time = time.time() print(end_time -start_time) return wrapper #返回嵌套的函数 @test #这句话加上后当运行foo()的时候,相当于会自动运行一遍 foo = test(foo)的操作 def foo(): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("hello CodeScrew") foo()
函数写到这里,觉得已经实现的很完美了,但实际上还有个漏洞,就是返回值还没有加上,试想我们运行foo(),实际上运行的是wrapper(),但是没把返回值给返回出来,加上返回值后为以下代码
import time def test(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 def wrapper(): #函数嵌套 start_time = time.time() res = Func() end_time = time.time() print(end_time -start_time) return res return wrapper #返回嵌套的函数 @test #这句话加上后当运行foo()的时候,相当于会自动运行一遍 foo = test(foo)的操作 def foo(): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("hello CodeScrew") return "OK" res = foo() print(res)
加入返回值之后,我们发现还有瑕疵,就是函数传参没加上。然后我们来加上函数传参
import time def test(Func): #定义一个高阶函数,这个高阶函数的特征是传参为函数名 def wrapper(*args,**kwargs): #函数嵌套 start_time = time.time() res = Func(*args,**kwargs) end_time = time.time() print(end_time -start_time) return res return wrapper #返回嵌套的函数 @test #这句话加上后当运行foo()的时候,相当于会自动运行一遍 foo = test(foo)的操作 def foo(name,age): #定义一个普通函数 time.sleep(0.5) #假装这个程序需要运行0.5秒,仅仅为了演示而加 print("name is %s,age is %d" %(name,age)) return "OK" res = foo("CodeScrew",25) print(res)
至此就实现了一个相对比较完美的装饰器了