7.1 装饰器
本质就是一个python函数,它可以在不改变函数任何代码的前提下,增加函数额外的功能
例如:记录日志,性能测试,用户登陆等
装饰器的返回值也是一个函数对象。
7.2 装饰器形成
测试该函数执行时间:
import time def func1(): print('in func1') def timer(func): def inner(): start_time = time.time() time.sleep(0.1) func() end_time = time.time() print('效率: %s' % (end_time - start_time)) return inner func1 = timer(func1) func1()
7.2.1 装饰器的语法糖 @(基本语法)
import time def timer(func): #1 timer(func1),调用该函数 def inner(): #3 start = time.time() #6 func() #7 print(time.time() - start) #9 return inner #4 @timer #2 func1 = timer(func1) def func1(): print('in func1') #8 func1() #5 # 这一个函数自上而下,先找哪里调用了它,先来到@timer,执行timer(func1),来到inner函数这里, # 但没有调用它的方法(inner()),所以跳过这个函数直接到return inner(注意这里return的是inner, # 这个很关键,父级返回子级,但是没有调用),接下来再从@timer以下去找哪里还有调用函数,找到func1(), # 现在func1()其实是timer(func1)(),(不懂看下面这个小的例子,自己试试),外层函数执行完返回inner, # 现在就是调用inner函数了,即timer(func1)() = inner().inner函数体内又包含一个函数的调用(func()), # 此时的func()由于timer(func1)这里形参赋值变成了func1(),在#7 这里对函数进行了调用,执行函数func1, # 打印#8,最后打印#9。 """ def fun(): a = 41 def fun1(): print(a, '啦啦啦') return fun1 fun()() """
7.2.2 被装饰函数带有参数的装饰器
import time def timer(func): def inner(*args, **kwargs): start = time.time() re = func(*args, **kwargs) end = time.time() print(end - start) return re # 我擦,又返回了这个func(*args, **kawrgs) return inner @timer # ==> jjj = timer(jjj) def jjj(a): print('in jjj and get a:%s' % (a)) return 'fun2 over' jjj('aaaaaa') print(jjj('aaaaaa')) # 运行这步fun2 over 才打印了
---in jjj and get a:aaaaaa
---0.0
---in jjj and get a:aaaaaa
---0.0
---fun2 over
7.2.3带有多个参数的装饰器
def wrapper1(func): def inner1(): print('wrapper1 ,before func') # 2 func() # 现在这里变成了f() print('wrapper1 ,after func') # 4 return inner1 def wrapper2(func): def inner2(): print('wrapper2 ,before func') # 1 func() # 现在这里成了inner1() print('wrapper2 ,after func') # 5 return inner2 @wrapper2 # f = wrapper2(f) 里面的f==inner1 外面的f == inner2 @wrapper1 # f = wrapper1(f) 里面的f==函数名f 外面的f == inner1
# 即inner2 = wrapper2(wrapper1(f)),先执行里面的wrapper1(f),return成了inner1,即inner2 = wrapper2(inner1),
执行wrapper2(inner1),return成了inner2,执行inner2函数,下来在执行inner1函数,在执行f()函数 def f(): # 3 print('in f') f()
---wrapper2 ,before func
---wrapper1 ,before func
---in f
---wrapper1 ,after func
---wrapper2 ,after func
# 优先执行靠近装饰函数的语法糖,即wrapper1
整个语法糖自上而下,先执行外层的wrapper2,wrapper1,
在执行wrapper1下的函数体,再执行里层的wrapper1,wrapper2
7.2.4 带参数的装饰器
def outer(flag): # outer(True) def timer(func): # timer(func) def inner(*args, **kwargs): if flag: print('''执行函数之前要做的''') re = func(*args, **kwargs) if flag: print('''执行函数之后要做的''') return re return inner return timer @outer(True) # func = outer(True)(func) def func(): print(111) func() # 这里装饰器所带参数为True,如果把它改为false则装饰器不起装饰作用,相当于去除了装饰功能。
7.2.5通用装饰公式
--------------------------记住了,这是固定套路---------------------------------
# 通用装饰器 def wrapper(func): def inner(*args, **kwargs): # 传入万能参数 '''执行函数前的操作''' ret = func(*args, **kwargs) '''执行函数后的操作''' return ret # 将函数的值返回 return inner # 添加装饰器 @wrapper def func(): print('通用装饰器') # 调用函数 func()
7.3 装饰器相关的扩展知识
import functools def wrapper(func): @functools.wraps(func) def inner(*args,**kwargs): return func(*args,**kwargs) return inner @wrapper def f1(): print('f1') @ wrapper def f2(): print('f2') print(f1.__name__) # f1 print(f2.__name__) # f2
关于functools模块的使用,这里由于使用了@functools.wraps(func),本来两者打印的都是inner,但是由于使用了它,输出f1,f2
作业题
1.写函数,返回一个扑克牌列表,里面有52项,每一项是一个元组
例如:[(‘红心’,2),(‘草花’,2), …(‘黑桃’,‘A’)]
def cards(): s = [(i, j) for i in ['黑', '红', '梅', '方'] for j in range(1, 14)] return s print(cards()) def cards(args): li = [] for i in args: for j in range(1, 14): li.append((i, j)) print(li) cards(['黑', '红', '梅', '方']) 标配呀,这个 def cards(*args): li = [] for i in ['黑', '红', '梅', '方']: for j in args: li.append((i, j)) return li print(cards(*(list(range(1, 11))), *['A', 'J', 'Q', 'K']))
2.写函数,传入n个数,返回字典{‘max’:最大值,’min’:最小值}
例如:min_max(2,5,7,8,4)
返回:{‘max’:8,’min’:2}
def min_max(*args): return dict([('max', max(args)), ('min', min(args))]) print(min_max(515,15,6,13,45))
3.写函数,专门计算图形的面积
其中嵌套函数,计算圆的面积,正方形的面积和长方形的面积
调用函数area(‘圆形’,圆半径) 返回圆的面积
调用函数area(‘正方形’,边长) 返回正方形的面积
调用函数area(‘长方形’,长,宽) 返回长方形的面积
def area():
def 计算长方形面积():
pass
def 计算正方形面积():
pass
def 计算圆形面积():
pass
def area(*args): def inner(): if args[0] == '长方形': return args[1]*args[2] if args[0] == '正方形': return args[1]*args[1] if args[0] == '圆形': return args[1]*args[1]*3.14 return inner() print(area('圆形', 3))
4.写函数,传入一个参数n,返回n的阶乘
例如:cal(7)
计算7*6*5*4*3*2*1
def cal(num): s = 1 for i in range(num, 0, -1): s = s*i return s print(cal(7)) def cal(num): if num == 1: return 1 return num*cal(num-1) print(cal(5))
5.给每个函数写一个记录日志的功能,
功能要求:每一次调用函数之前,要将函数名称,时间节点记录到log的日志中。
所需模块:
import time
struct_time = time.localtime()
print(time.strftime("%Y-%m-%d %H:%M:%S",struct_time))
import time def func(func2): def inner(): struct_time = time.localtime() with open('log.txt', mode='a', encoding='utf-8') as f: f.write('{} {} '.format(func2.__name__, time.strftime("%Y-%m-%d %H:%M:%S", struct_time))) func2() return inner @func def func1(): pass func1()
6、编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
dic = { 'username':None, 'status':False, } def wrapper(func): def inner(*args, **kwargs): if dic['status']: # 开始是False,所以先走else,用户账户密码正确下,这里改为True,就一直执行这步 ret = func(*args, **kwargs) return ret else: i = 0 while i < 3: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() with open('register_msg', encoding='utf-8') as f1: for j in f1: j_li = j.strip().split() # ['张三','123'] if username == j_li[0] and password == j_li[1]: dic['username'] = username dic['status'] = True ret = func(*args, **kwargs) return ret else: # 注意这里的else缩进,如果在if下面则会循环打印 print('账号或者密码错误,请重新输入%s机会' % (2-i)) i += 1 return inner @wrapper def article(): print('文章') @wrapper def diary(): print('日记') @wrapper def comment(): print('评论') @wrapper def file(): print('文件') article() diary() comment() file()
7.在编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码。这个作业之上进行升级操作:
设置两套密码,一套为京东账号密码,一套为淘宝账号密码保存在文件中。
设置四个函数,分别代表 京东首页,京东超市,淘宝首页,淘宝超市。
循环打印四个选项:东首页,京东超市,淘宝首页,淘宝超市。
供用户选择,用户输入选项后,执行该函数,四个函数都加上认证功能,只要登陆成功一次,在选择其他函数,后续都无需输入用户名和密码。
相关提示:用带参数的装饰器。装饰器内部加入判断,验证不同的账户密码。
# user_pwd文件内容: {'微信':{'username':'老男孩', 'password': '123'}, # 'qq':{'username':'老男孩1', 'password': '123'},} dic = { 'username':None, 'status':False, } def login(flag): def wrapper(func): def inner(*args, **kwargs): if dic['status']: ret = func(*args, **kwargs) return ret else: i = 0 while i < 3: username = input('请输入用户名(用%s账号):' % flag).strip() password = input('请输入密码:').strip() with open('user_pwd',encoding='utf-8') as f1: msg_dic = eval(f1.readline()) # 通过eval函数强制性将字符串转换成了字典 # {'微信': {'password': '123', 'username': '老男孩'}, # 'qq': {'password': '123', 'username': '老男孩1'}} if username == msg_dic[flag]['username'] and password == msg_dic[flag]['password']: dic['username'] = username dic['status'] = True ret = func(*args, **kwargs) return ret else: print('您输入的用户或者密码错误,请重新输入,还有%s次机会' % (2-i)) i += 1 return inner return wrapper @login('微信') def taobao_home(): print('淘宝首页') @login('微信') def taobao_shop(): print('淘宝超市') @login('qq') def jingdong_home(): print('京东首页') @login('qq') def jingdong_shop(): print('京东超市') choice_dict = { 1: taobao_home, 2: taobao_shop, 3: jingdong_home, 4: jingdong_shop, } while True: print('1 淘宝首页 2 淘宝超市 3 京东首页 4 京东超市') choice_num = input('请选择输入的序号:').strip() if choice_num.isdigit(): choice_num = int(choice_num) if 0 < choice_num <= len(choice_dict): choice_dict[choice_num]() else: print('请输入范围内的序号') else: print('您输入的有非法字符,请重新输入')