那么这里的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()通过传入参数,能够让我们避免对源代码的修改,但修改了函数的调用方式 通过返回值为函数名,能够让我们避免修改函数的调用方式 这时我们通过对添加功能函数的参数传入为函数,返回值为函数名,此时,我们满足了装饰器的两个原则。我们这种貌似是可取的,其实不然。我们可以发现在添加功能时,需要对函数执行一次才可以,这不符合我们的要求。 因此,仅仅只有高阶函数并不能满足于对我们的需求,不知道还是否记得开头写的对装饰器的知识储备。装饰器=高阶函数+函数嵌套+闭包。接下来,我们来了解什么时闭包吧。
(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.每一层的函数,我们可以成为一个包,而闭就是封装的意思,它封装的是变量,嵌套在函数内的函数等价于一个变量,遵循函数即变量的原则。其实闭包有点类似于作用域,若最里层需要一个变量,我们就可以在当前层定义,如果当前层不允许,则往上一层,一层一层的往外,也就是说我们可以在最外层定义一个变量,那就可以渗透到最里层闭包的最大用处也是用于装饰器,接下来,让我们看看装饰器的作用吧。事到如此,我们基本 完成了装饰器的功能了,我们不但没有修改函数的源代码,也没有修改其调用方式,还为它添加了一个新的功能。 不过,这样子还存在这一丢丢瑕疵,因为每次给函数添加功能是都需要做一次赋值操作,那能不能不每次调用都赋值一次? 那就要用到了一个 @,这是python提供的一个功能。 @timer 相当于 cal = timer(cal),我们要修饰那个函数,就在那个函数前添加@timer
输出的结果竟然是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)上面的例子,我们的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中就如位置可变参数和关键字可变参数,这样的话,我们就不用再担心被修饰函数有多少个参数,以及我们定义的装饰器可以修饰任何函数啦。