你真的会用PYTHON的装饰器了吗?(老铁)
本文结构:
- 无参装饰器的一般形式
a、 解决原生函数有参问题
b、解决原生函数有返回值问题
- 无参装饰器的模型总结
- 无参装饰器的具体应用实例
- 有参装饰器
- 有参装饰器的具体应用实例
对于大部分学Python的人来说,装饰器可能是遇到的第一个坎,装饰器到底是什么,到底应该怎么用?本篇博客将进行彻底的讲解。
装饰器的概念:
1、装饰器就是为了在不修改被装饰对象的源代码以及调用方式的前提下,为其添加新的功能;
2、装饰器本身可以是任何可调用的对象,被装饰的对象也可以是任意可调用的对象;
简单来说装饰器就是修改别人的工具,其中修饰指的是添加功能,工具指的是函数。
装饰器的语法:
假设被装饰的函数是index,请写出@timer的含义:
index = timer(index)
场景:假如我现在有3个函数,如下所示:
def index(): print('欢迎来到python世界') def home(): print('欢迎来到scala世界') def edit(): print('欢迎来到Java世界') index() home() edit()
现在的需求:求出每一个函数的运行时间:
渣渣aa的做法:
import time def index(): start_time = time.time() print('欢迎来到python世界') end_time = time.time() print('run time is %s'%(end_time-start_time)) def home(): start_time = time.time() print('欢迎来到scala世界') end_time = time.time() print('run time is %s'%(end_time-start_time)) def edit(): start_time = time.time() print('欢迎来到Java世界') end_time = time.time() print('run time is %s'%(end_time-start_time)) index() home() edit()
渣渣bb的做法:
def decorator(func): start_time = time.time() func() end_time = time.time() print('run time is %s'%(end_time-start_time)) def index(): print('欢迎来到python世界') def home(): print('欢迎来到scala世界') def edit(): print('欢迎来到Java世界') decorator(index) decorator(home) decorator(edit)
其中渣渣a的问题:修改了源代码;渣渣b的问题:修改了函数正常的调用方式。
问题产生了:我们如何在不修改被装饰对象的源代码以及调用方式的前提下为函数增加新的功能呢?这个时候我们的装饰器就上场了。
装饰器的解决方案:
import time def decorator(func): def wrapper(): start_time = time.time() func() end_time = time.time() print('run time is %s' % (end_time - start_time)) return wrapper @decorator def index(): print('欢迎来到python世界') @decorator def home(): print('欢迎来到scala世界') @decorator def edit(): print('欢迎来到Java世界') index() home() edit()
运行结果:
View Code
对于上面的代码,第一眼看上去是不是感觉很蒙圈,其实那是因为我中间省略了一个重要的步骤:
#!/usr/bin/python # -*- coding:utf-8 -*- import time def decorator(func): def wrapper(): start_time = time.time() func() end_time = time.time() print('run time is %s' % (end_time - start_time)) return wrapper def index(): print('欢迎来到python世界') index = decorator(index) print(index,' ',index.__name__) def home(): print('欢迎来到scala世界') home = decorator(home) def edit(): print('欢迎来到Java世界') edit = decorator(edit) index() home() edit()
随后在上一张图片:当执行完 index = decorator(index) 之后:返回的wrapper实际上是一个指针变量,所以index和wrapper将指向同一块内存空间,也就是说index已经不是我们之前的index了,而是悄悄的变成了闭包函数wrapper,wrapper在控制着index。
我想你也许发现哪里发生变化了,即index = decorato(index)等价于@decorator,对的,这就是装饰器的本质:Python的一种语法糖而已,装饰器就是闭包函数的一种实现。
上面我们使用的是简单的无参装饰器,但是对于上面的程序实际上还是有两个缺陷的:
假如3个原生函数是这样的:
View Code
如果我们还按照上面的方式去运行代码,将会抛出错误,因为当执行到func()的时候,实际上执行的是原始的index函数,但是我们在调用的时候并没有传入参数,所以:解决bug1:原生函数带有参数
import time def decorator(func): def wrapper(*args,**kwargs): #wrapper('小泡芙') start_time = time.time() func(*args,**kwargs) #执行到这里的时候会发现少了一个参数,因为原始的index函数必须传入一个参数 end_time = time.time() print('run time is %s' % (end_time - start_time)) return wrapper @decorator def index(name): print('欢迎来到python世界') @decorator def home(): print('欢迎来到scala世界') @decorator def edit(): print('欢迎来到Java世界') index('小泡芙') home() edit()
但是接下来问题又来了,如果原生函数带有返回值我们怎么获取呢?
@decorator def index(name): print('欢迎来到python世界') return 'Hello World' @decorator def home(): print('欢迎来到scala世界') @decorator def edit(): print('欢迎来到Java世界') print(index('小泡芙')) home() edit()
如果我们还按照以前的方式的话,你会发现:返回值将是None,为什么呢?因为wrapper函数的返回值就是None。
解决bug2:原生函数带有返回值
#!/usr/bin/python # -*- coding:utf-8 -*- import time def decorator(func): def wrapper(*args,**kwargs): #wrapper('小泡芙') start_time = time.time() res = func(*args,**kwargs) #执行到这里的时候会发现少了一个参数,因为原始的index函数必须传入一个参数 end_time = time.time() print('run time is %s' % (end_time - start_time)) return res return wrapper @decorator def index(name): print('欢迎来到python世界') return 'Hello World' @decorator def home(): print('欢迎来到scala世界') @decorator def edit(): print('欢迎来到Java世界') print(index('小泡芙')) home() edit()
运行结果:
欢迎来到python世界 run time is 0.0 Hello World 欢迎来到scala世界 run time is 0.0 欢迎来到Java世界 run time is 0.0 Process finished with exit code 0
到这里我们将引出常用无参装饰器的模型:
def decorator(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) return res return wrapper
接下来我们举一个具体的实例场景来说明装饰器的具体应用:要求函数在执行真正的代码之前,先实现一段认证功能,只有认证通过了,
才可以执行真正的功能。(该场景实际上是后期Django的cookie和session的应用)
#!/usr/bin/python # -*- coding:utf-8 -*- import time def decorator(func): def wrapper(*args,**kwargs): name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') return wrapper @decorator def index(): print('欢迎来到登陆页面') @decorator def home(): print('欢迎来到用户信息界面') @decorator def edit(): print('欢迎来到编辑界面') index() home() edit()
运行结果:
请输入用户名:eric 请输入密码:123456 登陆成功.... 欢迎来到登陆页面 请输入用户名:Jack 请输入密码:12346 登陆失败,您的用户名或者密码输入有误.. 请输入用户名:Angela 请输入密码:123 登陆失败,您的用户名或者密码输入有误..
但是上面的这个程序实际上还是存在问题,什么问题呢?这个问题就好比我已经登陆了京东的网页页面,但是后续无论我访问什么页面都需要再次登录一下登陆界面,这个问题确实有点扯。
解决方法:这个问题的真实场景是通过cookie或者session来记录用户登录状态的,但是在这里我们只能暂时通过全局变量来模拟这种效果了。
#!/usr/bin/python # -*- coding:utf-8 -*- import time user_info = {'user':None,'status':False} def decorator(func): def wrapper(*args,**kwargs): if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') return wrapper @decorator def index(): print('欢迎来到登陆页面') @decorator def home(): print('欢迎来到用户信息界面') @decorator def edit(): print('欢迎来到编辑界面') index() home() edit()
接下来运行结果就正常了:
请输入用户名:eric 请输入密码:123456 登陆成功.... 欢迎来到登陆页面 欢迎来到用户信息界面 欢迎来到编辑界面 Process finished with exit code 0
上面的无参装饰器我们讲完了,接下来我们来谈论有参装饰器的概念:
有参装饰器的模型:
def outer(driver='file'): def decorator(func): def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper return decorator
其实有参装饰器也没有想象中的那么难,本质上也是闭包函数和无参装饰器的扩展。
以上面的场景为例:用户认证的方式包括很多,如文件认证、数据库认证等等,如果利用有参装饰器进行实现呢?
代码示例:
#!/usr/bin/python # -*- coding:utf-8 -*- import time user_info = {'user':None,'status':False} def outer(auth='file'): def decorator(func): def wrapper(*args,**kwargs): if auth == 'file': print('file的验证方式') if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') elif auth == 'dba': print('dba的验证方式') if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') else: print('其余的验证方式') if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') return wrapper return decorator def index(): print('欢迎来到登陆页面') decorator = outer(auth='file') index = decorator(index) #index本质上还是wrapper print(index,index.__name__) def home(): print('欢迎来到用户信息界面') decorator = outer(auth='else') home = decorator(home) #home本质上还是wrapper print(home,home.__name__) def edit(): print('欢迎来到编辑界面') decorator = outer(auth='dba') edit = decorator(edit) #edit本质上还是wrapper print(edit,edit.__name__) index() home() edit()
我们写成装饰器的形式:
#!/usr/bin/python # -*- coding:utf-8 -*- import time user_info = {'user':None,'status':False} def outer(auth='file'): def decorator(func): def wrapper(*args,**kwargs): if auth == 'file': print('file的验证方式') if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') elif auth == 'dba': print('dba的验证方式') if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') else: print('其余的验证方式') if user_info['user'] and user_info['status']: res = func(*args, **kwargs) return res else: name = input('请输入用户名:') pwd = input('请输入密码:') if name == 'eric' and pwd == '123456': print('\033[42m登陆成功....\033[0m') # 用户一旦登陆成功,我们就将登陆成功的信息记录下来 user_info['user'] = 'eric' user_info['status'] = True res = func(*args,**kwargs) return res else: print('\033[42m登陆失败,您的用户名或者密码输入有误..\033[0m') return wrapper return decorator @outer(auth='file') def index(): print('欢迎来到登陆页面') @outer(auth='dba') def home(): print('欢迎来到用户信息界面') @outer(auth='else') def edit(): print('欢迎来到编辑界面') index() home() edit()
对于有参装饰器,我们注意两点就够了:
@outer(auth='file') ===> @decorator ==> index=decorator(index),也就是说,有参装饰器又给内部提供了一个参数。
实际上就是在最开始多了一个步骤,后面的步骤和我们上面是一模一样的。
OK,如有问题,欢迎留言指正。