python基础知识学习——装饰器

    xiaoxiao2023-10-15  45

    ** 装饰器**

    1.装饰器的概念

    装饰器的本质就是一个函数,它的作用是为其他函数添加一个新的功能,但是不改变原函数的源代码和调用方式。装饰器的两大原则: 不修改被修饰函数的源代码不修改被修饰函数的调用方式

    2.装饰器的知识储备(或者我们可以理解成,一个装饰器是由什么组成)

    装饰器 = 高阶函数+函数嵌套+闭包

    3.装饰器的实现

    首先我们定义一个累加求和的函数 import time def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res cal()

    这是我们的一个初始函数,如果此时我们需要为这个函数添加一个功能,例如我们需要计算这个函数需要的时间。想想好像也容易做到这一点的吧。通过调用函数前和函数运行结束后分别记下时间戳,那么我们就成功的为这个函数记下了运行的时间。 import time def cal(): start_time = time.time() res = 0 for i in range(100): res += i time.sleep(1) stop_time = time.time() print("函数的运行结果为", res) print("函数的运行时间为",stop_time-start_time) return res cal()

    通过上面的这种方法,我们很容易就达到了我们需要做得目的了,也就是说我们成功的为函数添加了一个新的功能——记录函数运行的时间。不过,这只是其中的一个函数,如果说有成百上千个函数,我们也要这样一个个的添加功能吗?这显然不太现实。更关键的一点,我们这种做法已经违反了开放封闭原则。开放封闭原则简单的理解就是我们写的程序上线以后,我们就不能对程序的源代码经行修改。如果函数在其他位置被调用了的话,我们便不知道会不会出现其他的后果,有可能引起一系列的连锁反应。所以说,上面的方法,我们是行不通的。装饰器的第二个原则:不修改被修饰函数的调用方式,我可以为函数添加新的功能,但是不能改变它的调用方式,在上面的函数中,我们通过cal()进行调用函数,那么我们添加了功能之后,我们就必须按照原来的方法,用cal()来调用函数。 高阶函数的使用 首先我们说一下,什么是高阶函数? 满足下面两个条件中的其中一个都可以成为高阶函数: 函数接受的参数是一个函数名函数的返回值是一个变量名 我们继续拿上面的函数做例子 import time def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res def test(func): print(func) func() test(cal)

    那么这里的test就是一个高阶函数。因为它接收的参数是一个函数名func,看到这里,我们就可以进行对函数cal添加新的功能了,继续以记录函数运行时间作为一个例子。

    import time def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res def test(func): print(func) start_time = time.time() func() stop_time = time.time() print("函数运行的时间为",stop_time-start_time ) test(cal)

    来看下这个函数运行的结果: 那这时候,我们不但没有修改原函数的源代码,而且还为函数添加了一个新的功能,我们的功能就实现了,装饰器就这么结束了。难道真的结束了吗?你注意到了没,函数的方式已经发生改变了,初始我们调用函数使用cal(),而现在却是test(cal),这就违反了我们上述所说的装饰器的第二原则。 那么我们来看下高阶函数第二种定义:返回值是一个函数名

    import time def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res def test(func): start_time = time.time() cal() stop_time = time.time() print("函数的运行时间为",stop_time-start_time) return func cal = test(cal) # print(res) cal()

    通过传入参数,能够让我们避免对源代码的修改,但修改了函数的调用方式 通过返回值为函数名,能够让我们避免修改函数的调用方式 这时我们通过对添加功能函数的参数传入为函数,返回值为函数名,此时,我们满足了装饰器的两个原则。我们这种貌似是可取的,其实不然。我们可以发现在添加功能时,需要对函数执行一次才可以,这不符合我们的要求。 因此,仅仅只有高阶函数并不能满足于对我们的需求,不知道还是否记得开头写的对装饰器的知识储备。装饰器=高阶函数+函数嵌套+闭包。接下来,我们来了解什么时闭包吧。

    4.闭包

    闭包的理解:有很多小伙伴都不懂什么是什么,比较模糊,其实我们可以理解闭包为一种特殊的嵌套函数,这个函数由两个函数嵌套组成,那么在结构上就有外函数和内函数的说法。外函数返回值是内函数的引用结果

    (1)外层函数out_func可以调用内层函数in_func,但无法引用in_func内部的变量y

    (2)内层函数in_func可以引用外层函数out_func的变量x

    def out_func(): x = 1 def in_func(y): return x+y return in_func test = out_func() print(test(10)) test是闭包函数,自由变量是x,因为它不是in_func这个函数内部的变量,但是却被in_func引用。test的结果是函数in_func的地址,那么通过test()便可调用函数in_test.每一层的函数,我们可以成为一个包,而闭就是封装的意思,它封装的是变量,嵌套在函数内的函数等价于一个变量,遵循函数即变量的原则。其实闭包有点类似于作用域,若最里层需要一个变量,我们就可以在当前层定义,如果当前层不允许,则往上一层,一层一层的往外,也就是说我们可以在最外层定义一个变量,那就可以渗透到最里层闭包的最大用处也是用于装饰器,接下来,让我们看看装饰器的作用吧。

    5.装饰器的实现

    我们继续以上面的例子来了解装饰器,我们要为一个函数计算运行时间。 来,先看一段代码吧。 import time def timer(func): def wrapper(): # print(func) start_time = time.time() func() stop_time = time.time() print(stop_time-start_time) return wrapper #@timer #等价于 cal = timer(cal) def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res cal = timer(cal) cal()

    事到如此,我们基本 完成了装饰器的功能了,我们不但没有修改函数的源代码,也没有修改其调用方式,还为它添加了一个新的功能。 不过,这样子还存在这一丢丢瑕疵,因为每次给函数添加功能是都需要做一次赋值操作,那能不能不每次调用都赋值一次? 那就要用到了一个 @,这是python提供的一个功能。 @timer 相当于 cal = timer(cal),我们要修饰那个函数,就在那个函数前添加@timer

    6.含返回值的装饰器

    上面的样子看上去似乎已经满足了我们的要求了,但好像还有点小问题额。。。如果我们需要用到cal函数的返回结果的时候,这貌似不符合我们的心意啊。 res = cal() print("这是函数的返回结果的cal:",res)

    输出的结果竟然是None?????

    仔细研究一下,cal经过装饰后,本质上就是上面的经过升级后的wrapper函数,而这个函数是没有返回值的,因此,cal()运行的结果返回就是默认的None啦。 def wrapper(): start_time = time.time() func() stop_time = time.time() print(stop_time-start_time) 来看看这一段wrapper函数,我们需要func的返回值,那么就需要在wrapper函数加上return返回咯,返回的值是func的结果,那就简单了,吧func返回的结果赋值给变量res,然后return返回res这样就解决啦。 import time def timer(func): def wrapper(): # print(func) start_time = time.time() res = func() stop_time = time.time() print(stop_time-start_time) return res return wrapper @timer def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res res = cal() print("这是函数的返回结果的cal:",res)

    7.含参数的装饰器

    上面的例子,我们的cal函数都是不带参数的,但是,我们并不能保证以后我们所写的每一个函数都是不带参数的。

    举个例子吧,我们在原来的基础上定义了一个test1函数: def timer(func): def wrapper(): # print(func) start_time = time.time() res = func() stop_time = time.time() return res return wrapper @timer def cal(): res = 0 for i in range(100): res+=i time.sleep(1) print("函数的运行结果为",res) return res @timer def test1(name): time.sleep(1) print("这是%s"%(name)) return res

    我们在不修改装饰器的情况下,就会报错了,wrapped函数不需要函数,但是一个给出了。 那么我们就要在wrapped函数中加上参数name,不过,依然不能解决问题。 这是我们就需要用到了可变参数了。 如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用*args; 如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使用**kwargs。

    def timer(func): def wrapper(*args,**kwargs): # print(func) start_time = time.time() res = func(*args,**kwargs) stop_time = time.time() return res return wrapper

    这里我们对wrapper函数进行修改,在wrapper中就如位置可变参数和关键字可变参数,这样的话,我们就不用再担心被修饰函数有多少个参数,以及我们定义的装饰器可以修饰任何函数啦。

    好啦,装饰器就到此结束了。。。。。。
    最新回复(0)