Python入门自学进阶——1--装饰器
Posted kaoa000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶——1--装饰器相关的知识,希望对你有一定的参考价值。
理解装饰器,先要理解函数和高阶函数。
首先要明白,函数名就是一个变量,如下图,定义一个变量名和定义一个函数,函数名与变量名是等价的。
既然函数名就是一个变量名,那么在定义函数时,函数的参数就可以是函数名,即可以传递一个函数给另一个函数。同样,函数可以返回一个变量,如return x,那么,这个x既可以是一个变量名,也就可以是一个函数名。
对于foo1,返回的是inner这个函数对象,如果是return inner(),那就是返回8,函数名(),即函数名加上括号,就是对函数对象的调用,返回的是调用对象的返回值。
当函数的参数中包含函数名,或者作为返回值返回时,这个函数就叫做高阶函数,如上面的foo函数和foo1函数。
其次要理解变量的作用域,即LEGB
L——Local,局部变量,E——Enclosing,嵌套的父级函数局部变量,G——Global,全局变量,B——Build-in,系统模块固定的变量。
变量的查找顺序是L——>E——>G——>B.
第三要理解闭包的概念。
对于上面的函数,外层outer()函数内定义了一个内部函数inner(),outer返回的是inner,对于返回的inner函数来说,按照前面的作用域LEGB原则,f,即返回的inner函数是无法访问outer函数的局部变量x的,但是,这种写法inner就能访问,这叫做闭包。即返回的内部函数能够访问一个外部变量。闭包=函数块+定义函数时的环境
装饰器的引入
有很多开发完毕的函数,已经被大量使用,如有一个函数:
def foo():
print('foo func...')
现在想给这个函数加一个功能,计算这个函数的运行时间,一个办法是直接修改foo函数:
import time
def foo():
start = time.time()
print('foo func...')
end = timt.time()
print('执行时间 %s' % (end-start))
这种方法虽然实现了功能,但是对于已经发布大量使用的函数,是不能这样的修改的。一个原则:开放封闭原则,对修改源代码封闭,对在不修改源代码的前提下对扩展开放。因为原函数已经大量在生产环境应用,直接修改源代码增加功能,代价太大,无法承受。另一方面,如果多个函数都要增加这个功能,如还有一个函数bar,也要增加一个计算运行时间的功能,按上面的写法;
import time
def bar():
start = time.time()
print('bar func...')
end = timt.time()
print('执行时间 %s' % (end-start))
代码会大量重复。解决重复的方法,就是重复的代码再写成一个函数。
def showtime():
start = time.time()
foo()
end = timt.time()
print('执行时间 %s' % (end-start))
这样,就实现了功能。但是这只能测试一个foo函数,要测试任意函数,修改如下:
def showtime(func):
start = time.time()
func()
end = timt.time()
print('执行时间 %s' % (end-start))
按照前面的高阶函数,函数名可以作为参数传递,将showtime的参数设为函数名,想测试哪个函数,就将哪个函数名传进去。
现在功能是实现了,但是我们原来的本意是对原函数扩展功能,上面的实现方法,实际上是新定义了一个函数,其他调用原函数的程序要想使用这个功能,需要大量修改程序,这就改变了函数的调用方式。我们想要的是函数名不变而功能实现,即调用方式不能变。
现在的需求就是我的调用还是foo(),但实现的是showtime(foo)这个功能。
将showtime函数进行修改:
def showtime(func):
def inner():
start = time.time()
func()
end = timt.time()
print('执行时间 %s' % (end-start))
return inner
foo = showtime(foo) #返回的是inner函数的引用。
foo() #这其实就是执行inner()函数,inner函数是一个闭包函数,所以它可以在脱离showtime函数后,依然能够使用外部的变量,这里就是func这个参数变量。
要特别注意的是理解:foo = showtime(foo),赋值运算是先计算等号右边的式子,在进行赋值,所以先运行showtime(foo),这个foo是原函数,即这个foo是原函数的引用。赋值操作发生后,foo变成了inner的引用。这里有些混乱的是inner中的func(),字面上还应该是foo,指向的源foo函数的引用,调用的foo(),这个foo是inner。
这里showtime函数就是一个装饰器函数。
如果要扩展其他函数功能,如bar函数,只要:
bar = showtime(bar)
bar()
源函数没有改变,调用方式也没有改变。
python对装饰器的解决方法:
python将bar=showtime(bar)改为在函数上方加上@showtime来解决:
@showtime
def bar():
print('bar func...')
再研究一下bar=showtime(bar):
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
函数对象有一个__name__属性,可以拿到函数的名字:
经过装饰器装饰后的函数,实际是函数名与源函数名相同,但是__name__是一个装饰器中的闭包函数,这里是inner。
对于装饰器,使用的是@showtime,相当于bar=showtime(bar),showtime是有参数的,这个参数f实际传入的就是紧跟@showtime后的def定义的函数名。
如果我们要装饰的函数带有参数,如:
def myadd(a,b):
print(a+b)
或者一个参数不固定的函数
def myadd1(*a,**b)
对于第一种,需要修改inner的参数:
第二种:
以上是对原函数,即功能函数加参数,同时要在inner中增加相应的参数。
新需求,现在在计算运行时间的同时,还要进行日志操作。但是只是对部分函数要求进行日志操作。
上面的方法不行,因为所有的函数加装饰器后都运行了loger,想要针对不同函数运行不同的装饰功能,需要加一个参数进行控制,是否可以在showtime上加呢?如下:
def showtime(f,flag),这是不可以的,因为装饰器的使用是是固定的@showtime,不带参数的,修改showtime如下:
这里关键是理解@log('T'),这一句是要先执行log(‘T’),log返回的是showtime,最后就变成了@showtime,此时与前面不同的是,showtime中代入了flag参数,又是一个闭包。
运行结果:
以上是关于Python入门自学进阶——1--装饰器的主要内容,如果未能解决你的问题,请参考以下文章