python学习系列:装饰器
如果有好多函数,有天老大需要我们给这些函数添加上某些功能,但是不能修改原函数的代码,也不能改变其运行方式,怎么办?
比如我们有如下两个函数:
import time def test1(): time.sleep(1) #模拟函数运行时间 print("this is test1 function") def tests2(): time.sleep(1) #模拟函数运行时间 print("this is test2 function")
需求:获取到上面两个函数的运行时间,该如何实现?
第一种方式,就是逐个修改函数,将需要添加的功能代码加上去:
import time def test1(): start_time = time.time() time.sleep(1) #模拟函数运行时间 print("this is test1 function") stop_time = time.time() print("func run time is %s"%(stop_time-start_time)) def test2(): start_time = time.time() time.sleep(1) #模拟函数运行时间 print("this is test2 function") stop_time = time.time() print("func run time is %s"%(stop_time-start_time)) test1() test2()
运行结果:
上面的方法虽然可以实现,但是非常的笨拙。我们有没有其他的办法,让我们先看看用装饰器是怎么来实现:
import time def timmer(func): def wrapper(*args,**kwargs): start_time = time.time() func() stop_time = time.time() print("func run time is %s"%(stop_time-start_time)) return wrapper @timmer def test1(): time.sleep(1) print("this is test1 function") @timmer def test2(): time.sleep(1) print("this is test2 function") test1() test2()
运行结果:
通过上面的代码我们看到了通过装饰器是怎么来实现既不修改原函数代码也不修改原函数的运行方式来实现了给函数添加功能。
接下来我们来详细探究一下,装饰器是如何来实现的,先看下面这段代码:
import time def timmer(func): def wrapper(): start_time = time.time() func() stop_time = time.time() print("func run time is %s"%(stop_time-start_time)) return wrapper def test1(): time.sleep(1) print("this is test1 function") def test2(): time.sleep(1) print("this is test2 function") test1 = timmer(test1) #函数timmer接收test1函数的指向,返回的是wrapper函数指向。那么test1其实此时是指向wrapper函数体的指针 test2 = timmer(test2) #函数timmer接收test2函数的指向,返回的是wrapper函数指向。那么test2其实此时是指向wrapper函数体的指针 test1() #此时其实执行的是wrapper函数,wrapper函数里面的func指向的是原来的test1函数。 test2() #与test1的原理一样。
由此我们可以看出@timmer 等价于 test1 = timmer(test1)
现在我们基本上实现了我们自己的装饰器,但是我们遇到一个问题,如果我们的函数需要传入参数,比如我们的test2函数是下面这样的:
def test2(name): time.sleep(1) print(name) print("this is test2 function")
运行结果:
TypeError: test2() takes exactly 1 argument (0 given)
为什么会出现这样的情况:
这是因为 wrapper函数里面法func()是没有带参数的,我们通过test2 = timmer(test2),将test2存储的函数体房间号转给了func存储。
但是func是没有带参数的,所以会报上面的错误:需要一个参数,但是一个也没给。我们怎么解决呢?
test1 = timmer(test1) 其实test1已经指向了wrapper函数,此时只需要将参数传给wrapper。代码如下:
import time def timmer(func): def wrapper(*args,**kwargs): start_time = time.time() func(*args,**kwargs) stop_time = time.time() print("func run time is %s"%(stop_time-start_time)) return wrapper def test2(): print("test2 is running") def test1(name): print(name) print("test1 is running") test1 = timmer(test1) test2 = timmer(test2) test1("book") test2()
运行结果:
"D:\Program Files\Anaconda3\python.exe" C:/Users/mis.WISDOM/PycharmProjects/erpgg/venv/week4/装饰器.py book test1 is running func run time is 0.0 test2 is running func run time is 0.0 Process finished with exit code 0
顺便补一下返回值:
import time def timmer(func): def wrapper(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) #将返回值用res接收到 stop_time = time.time() print("func run time is %s"%(stop_time-start_time)) return res #将res返回 return wrapper #timmer #等价于test2 = timmer(test2) def test2(): print("test2 is running")
@timmer #等价于test1 = timmer(test1) def test1(name): print(name) print("test1 is running") return "good"
t1 = test1("book") #t1接收test1的返回值。
t2 = test2() #t2接受到test2的返回值
print(t1,t2)
装饰器的终极升级(函数的双层嵌套):
需求说明:home页面需要用用户名密码进行认证,bbs页面需要用ldap服务进行认证,index页面不需要认证。
user,passwd = 'root','123456' def auth(type): def out_wrapper(func): def wrapper(*args,**kwargs): if(type=='user'): username = input("please input username") password = input("please input password") if username == user and password == passwd: res = func(*args,**kwargs) return res else: print("Invalid username or password!!") elif(type=='ldap'): print("Ldap认证功能正在开发中") return wrapper return out_wrapper def index(): print("welcome to index !") @auth('user') def home(): print("welcome to home!") @auth('ldap') def bbs(): print("welcome to bbs!") index() home() bbs()
运行结果:
"D:\Program Files\Anaconda3\python.exe" C:/Users/mis.WISDOM/PycharmProjects/erpgg/venv/week4/装饰器.py welcome to index ! please input usernameroot please input password123456 welcome to home! Ldap认证功能正在开发中 Process finished with exit code 0