装饰器

Posted 代码螺丝钉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装饰器相关的知识,希望对你有一定的参考价值。

什么是装饰器


从字面意义来理解“装饰器”这三个字,器指的就是函数,所以装饰器本质是一个函数,功能是为其他函数添加附加功能,举个简单的例子,一段程序你想为其增加一段统计运行时间的功能

原则:

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)

至此就实现了一个相对比较完美的装饰器了

 

以上是关于装饰器的主要内容,如果未能解决你的问题,请参考以下文章

Python面向对象学习之八,装饰器

thymeleaf 片段渲染后重新加载 javascript

代码缺乏装饰?使用ts装饰器来装饰你的代码

代码缺乏装饰?使用ts装饰器来装饰你的代码

代码缺乏装饰?使用ts装饰器来装饰你的代码

代码缺乏装饰?使用ts装饰器来装饰你的代码