python——装饰器
什么是装饰器:本质就是函数,为其他函数添加附加功能
装饰器原则:(1)不修改被修饰函数的源代码
(2)不修改被修饰函数的调用方式
装饰器 = 高阶函数+函数嵌套+闭包
1.高阶函数修饰函数 为foo函数添加计算运行时间功能
import time def foo(): time.sleep(3) print('来自foo') def timer(func): 传入参数为函数,实现了为foo函数添加计时功能 start_time = time.time() func() end_time = time.time() print('函数运行时间',(end_time - start_time)) return func return函数实现了被修饰函数调用方式不变,此处return foo 是为了保证当 foo = timer(foo)时,运行foo()功能不变 foo = timer(foo) foo()
运行结果:
来自foo
函数运行时间 -3.000277519226074
来自foo 因为上面 return func导致foo函数被运行了一遍
可以发现,timer实现了在不改变被修饰函数的源代码以及调用方式的基础上为函数添加了计时功能,但缺陷在于foo函数运行了两次
2.函数嵌套: 函数内部定义新函数 每一层函数就是一个包
3.装饰器框架
def timer(func): def wrapper(): 函数内部定义一个函数 print(func) func() return wrapper 函数返回值是函数
4.运用嵌套+闭包 进行完善
import time def foo(): time.sleep(3) print('来自foo') def timer(func): def wrapper(): start_time = time.time() func() end_time = time.time() print('函数运行时间',(end_time - start_time)) return wrapper # foo = timer(foo) foo()
此时可以发现foo被运行两边的缺陷被完善了,因为在内部定义了一个函数,并在此函数内部定义功能,然后timer的返回值是此函数
(此函数内运行了foo()函数,又避免了返回值在调用一遍foo函数),但此时仍然存在缺陷,就是需要进行 foo = timer(foo)操作,
5.@timer 功能相当于 foo = timer(foo)
import time def timer(func): 为原函数添加功能的函数需在上面 def wrapper(): start_time = time.time() func() end_time = time.time() print('函数运行时间',(end_time - start_time)) return wrapper @timer 在原函数上一行加@timer def foo(): time.sleep(3) print('来自foo') foo() 运行结果: 来自foo 函数运行时间 3.000234365463257
此时仍有一个问题,即函数foo()的返回值,因为调用了 @timer 此时会导致 foo=timer(foo) 所以调用foo() 实际是在执行timer(foo)() (此时如果print(foo),会打印wrapper的
内存地 址),分析timer可知其返回值为 wrapper也就是说foo() = wrapper(),也就是说,如果函数foo内有return,调用res = foo()其返回值不会赋值给res,而会把wrapper 函数的返回值赋值给res
要解决此问题,看6.中的代码及注释
6.带参数的函数的装饰器 当被装饰函数带参数时,装饰器函数需要设置形参为*args和**kwargs,因为如果有两个函数,一个待产如参数为两个,另一个带传入参数为三个,而它们都需要增加同一个功能,
若装饰器函数为了满足第一个被装饰函数的条件,设置两个形参,则运行后将会报错,因此将形参设置为*args和**kwargs,不论被装饰函数带几个参数,都不会报错
import time def timer(func): 此处传入foo,即timer(foo) def wrapper(*args,**kwargs): 根据调用语句 ret = foo(),此处args = ('jiaoguohua',18) ,**kwargs = {} start_time = time.time() res = func(*args,**kwargs) 此处即为foo(*args,**kwargs),由上面可知,此处为foo(*('jiaoguohua',18),**{}),根据其特性,前面加*号,则会将元组及字典中的值挨个传入,因此其实就相当于foo('jiaoguohua',18),因此,符合foo定义时两个参数的要求 end_time = time.time() print('函数运行时间',(end_time - start_time)) return res 若想在调用ret = foo(),使ret获得foo()函数定义内的返回值,则需要使用语句res = func(*args,**kwargs)获得foo()的返回值,并在此处返回res,即将foo()的返回值返回给wrapper return wrapper @timer #foo = timer (foo) def foo(name,age,): time.sleep(1) print('我的名字时%s,年龄%s' %(name,age)) return '这是foo的返回值' ret = foo('jiaoguohua',18) print(res) 运行结果:
我的名字时jiaoguohua,年龄18 函数运行时间 1.0009615421295166 这是foo的返回值
总结:由以上可知,装饰器的框架为
def timer(func): def wrapper(*args,**kwargs): #要添加的功能... res = func(*args,**kwargs) #..... return res return wrapper
应用:以京东为例,运用装饰器的相关知识,加入验证功能,
(1)如果用户名为'jiao' 密码为'123'则运行函数
user_dic = {'name':None,'login':False} 定义这个字典(此字典为全局变量)是为了不在用户名和密码输入正确时,重复运行输入用户名和密码的操作才能运行下一个函数
def auth_func(func): def wrapper(*args,**kwargs):
if user_dic['name'] and user_dic['login'] 此处if语句的作用是:当第一次输入用户名和密码正确后将其赋值给user_dic 的两个值,下一次运行时将直接执行此语句,执行到return res结束此次函数调用,而不用再执行后面的if语句而导致再一次输入用户名和密码
res = func(*args,**kwargs} (因为后面是调用了三个函数,每个函数都会调用此函数)
return res
name = input('请输入用户名:').strip() passwd = input('请输入密码:').strip() if name == 'jiao' and passwd =='123':
user_dic{'name'} = 'name' 此处设置当用户名和密码输入正确时,将user_dic字典的两个值进行修改,使其布尔值都为正确(注意 键'name'的值修改为传入的字符出串,齐布尔值也为正确)
user_dic{'login'} = True res = func(*args,**kwargs) else: print('用户名或密码错误')
return res return wrapper @auth_func def index(): print('欢迎来到京东主页') @auth_func def home(name): print('欢迎回家%s'%name) @auth_func def shopping_car(name): print('%s的购物车里有奶茶、衣服、鞋子' %name) index() home('产品经理') shopping_car('产品经理')
只要是以下用户,则运行函数
user_list = [{'name':'jiao','passwd':'123'}, {'name':'guo','passwd':'123'}, {'name':'hua','passwd':'123'}] current_user = {'name':'None','login':False} def auth_func(func): def wrapper(*args,**kwargs): if current_user['name'] and current_user['login']: res = func(*args,**kwargs) return res user_name = input('请输入用户名:').strip() passwd = input('请输入密码:').strip() for user_dic in user_list: if user_name == user_dic['name'] and passwd == user_dic['passwd']: current_user['name']= user_name current_user['login'] = True res = func(*args,**kwargs) return res else: 此处else不是与if同缩进,而是与for一起,是为了避免在用户名输错时就运行else语句(要等到用户名跟密码都判断过后),此else语句是for中if语句判断都不成立时运行此语句 print('用户名或密码错误') return wrapper @auth_func def index(): print('欢迎来到京东主页') @auth_func def home(name): print('欢迎回家%s'%name) @auth_func def shopping_car(name): print('%s的购物车里有奶茶、衣服、鞋子' %name) index() home('产品经理') shopping_car('产品经理')
7.带参数的装饰器 (注意与6. 的区别,6是函数带参数,7.是装饰器带参数 )
作用:以上述京东为例,可以增加不同的验证方式(上述是名字+密码),通过传入不同的装饰器参数选择不同的验证方式
user_list = [{'name':'jiao','passwd':'123'}, {'name':'guo','passwd':'123'}, {'name':'hua','passwd':'123'}] current_user = {'name':'None','login':False} def auth(auth_type ): 在auth_func的外层在定义一层函数auth(auth_type),这样就可以再往里传入一个参数,即可以根据此传入的参数的不同来选择执行那段代码(通过if语句),此处是为了有更多的验证方式可以选择
def auth_func(func): 整体缩进一个tab def wrapper(*args,**kwargs): if auth_type == 'ldb': 因为对于一个装饰器来说,调用原来的函数(如index())其实最终就是在调用wrapper(),运行其内部代码,因此,在其内部设置if语句,根据传入的wuth_type判断是否执行对应的功能,此处为是否采用这种验证方式 print('这是ldb验证方式') if current_user['name'] and current_user['login']: res = func(*args,**kwargs) return res user_name = input('请输入用户名:').strip() passwd = input('请输入密码:').strip() for user_dic in user_list: if user_name == user_dic['name'] and passwd == user_dic['passwd']: current_user['name']= user_name current_user['login'] = True res = func(*args,**kwargs) return res else: print('用户名或密码错误') if auth_type == 'jgh': 此处定义了第二种验证方式,即传入的 auth_type 为 ‘jgh’时,执行内部的语句,此处亦可以向前一个代码一样设置另一种验证方式,此处能力有限,从简处理 print('这是jgh验证方式') print('这是第二种验证方式')
res = func(*args,**kwargs)
return res
if auth_type == 'sss': print('这是sss验证方式') print('这是第三种验证方式')
res = func(*args,**kwargs)
return res
return wrapper return auth_func @auth(auth_type = 'ldb') 此时调用方式变为@auth(传入一个形参),通过上述代码可以知道,auth()运行后返回auth_func,所以说此处其实就相当于@auth_func,不同之处在于为auth_func传入了一个参数auth_type def index(): print('欢迎来到京东主页') @auth(auth_type = 'jgh') 可以选择传入auth_type的参数从而选择不同的验证方式 def home(name): print('欢迎回家%s'%name) @auth(auth_type = 'sss') def shopping_car(name): print('%s的购物车里有奶茶、衣服、鞋子' %name) index() home('产品经理') shopping_car('产品经理')
通过上述代码可以发现,即使改变了调用方式,即从@auth_func 变为@auth(auth_type = ' ...') , 但其实还是相当于@auth_func 不同之处在于用这种方法,为auth_func(func)函数传入了一个参数,
这样,就可以根据不同auth_type,设置不同的代码(通过if语句),用户就可以根据传入参数的不同而选择不同的代码去执行,完成不同的功能。 这就是闭包的概念和功能