一 什么是装饰器
装饰器的实现是函数里面嵌套函数
装饰器的本质是一个函数,它可以让其他函数在不需要作任何代码改动的前提下增加额外功能
装饰器需要传递一个函数,返回值也是一个函数对象
二 装饰器的应用
在函数执行之前和执行之后添加功能,调用函数的方式改变到了
不改变原有函数的调用方式:函数里面嵌套函数,并且返回嵌套的函数
def desc(fun):
def addinfo():
print('welcome')
fun()
print('seeyou')
return addinfo
def login():
print('login...')
login=desc(login) #返回值是一个函数
login()
1) # 装饰器需求:获取函数执行的时间
函数执行之前的时间;函数执行之后的时间
看字符串拼接的效率:
1)相加
2)join 方法
import random
import string
import time
li= [random.choice(string.ascii_letters)for i in range(100)]
def timeit(fun):
def wrapper():
#函数执行之前
start_time=time.time()
#执行函数
fun()
#函数执行之后
stop_time=time.time()
print('time:%.6f'%(stop_time-start_time))
return wrapper
@timeit
def con_add():
s=''
for i in li:
s+=(i+',')
print(s)
@timeit
def join_add():
print(','.join(li))
con_add()
join_add()
2)检测列表生成式和map的效率高低,n为函数传入的参数
1)[i**2 for i in range(n)]
2) map(lambda x:x**2 range(n))
def timeit(fun):
"""这是一个装饰器timeit"""
def wrapper(*args, **kwargs): # 接收可变参数和关键字参数 假使传入的值n=5
"""这是一个wrapper函数"""
# args:元组 kwargs:字典 此处就为 args=(5,)
# 函数执行之前
start_time = time.time()
# 执行函数
res = fun(*args, **kwargs) # 需要传入的是一个值,显然元组是不行的,因此需要对参数进行解包:*args=5
# 对于元组,字典进行解包
# 函数执行之后
stop_time = time.time()
print('time:%.6f' % (stop_time - start_time))
return res
return wrapper
@timeit
def list_shi(n):
"""这是list函数"""
return [i ** 2 for i in range(n)]
list_shi(1000)
@timeit
def map_shi(n):
return (map(lambda x: x ** 2, range(n)))
map_shi(1000)
print(list_shi.__name__)
print(list_shi.__doc__)
可以看到map明显更快点,最后两行代码,输出的是list_shi的信息,但是最后却显示的是装饰器的信息,那么如何来保留被装饰函数的信息呢
这里就需要加入 functools模块
import time
import functools
def timeit(fun):
"""这是一个装饰器timeit"""
@functools.wraps(fun)
# 可以保留被装饰函数的函数名和帮助文档信息
def wrapper(*args, **kwargs): # 接收可变参数和关键字参数 假使传入的值n=5
"""这是一个wrapper函数"""
# args:元组 kwargs:字典 此处就为 args=(5,)
# 函数执行之前
start_time = time.time()
# 执行函数
res = fun(*args, **kwargs) # 需要传入的是一个值,显然元组是不行的,因此需要对参数进行解包:*args=5
# 对于元组,字典进行解包
# 函数执行之后
stop_time = time.time()
print('time:%.6f' % (stop_time - start_time))
return res
return wrapper
@timeit
def list_shi(n):
"""这是list函数"""
return [i ** 2 for i in range(n)]
list_shi(1000)
@timeit
def map_shi(n):
return (map(lambda x: x ** 2, range(n)))
map_shi(1000)
print(list_shi.__name__)
print(list_shi.__doc__)
这样可以看到输出的是被装饰函数的函数名和函数帮助文档
三 装饰器日志
创建装饰器 要求如下:
1.创建add_log装饰器被装饰的函数打印日志信息
2.日志格式为:[字符串时间],函数名:xxx,运行时间:xxx,运行返回值结果:xxx
import string
import time
import functools
def add_log(fun):
@functools.wraps(fun)
def wrapper(*args, **kwargs):
start_time = time.time()
res = fun(*args, **kwargs)
end_time = time.time()
print('[%s]\n函数名:%s\n运行时间:%.5f\n运行返回值结果:%d'
% (time.ctime(), add.__name__, end_time - start_time, res))
# time.ctime()显示的时间为 Mon Aug 20 00:52:37 2018 这种格式
return res
return wrapper
@add_log
def add(x, y):
time.sleep(0.1) # 暂停0.1s
return x + y
add(1, 2)
可以看到运行结果符合输出形式
四 装饰器登陆网站
需求:用户登陆验证装饰器id_login
1.如果用户登陆成功,则执行被装饰的函数
2.如果用户未登陆,则执行登陆函数
import functools
login_users = ['admin', 'root']
def is_login(fun): # fun:writeblog
@functools.wraps(fun)
def wrapper(*args, **kwargs): # name='admin' #kwargs={name:'admin'}
# 判断写博客的这个用户是否登陆成功
if kwargs.get('name') in login_users:
res = fun(*args, **kwargs)
else:
res = login()
return res
return wrapper
# 必须登陆成功
@is_login # writeblog=is_login(writeblog)
def writeblog(name):
return 'write blog'
def login():
return 'login'
# 是否登陆都可以执行的函数
def news():
print('news')
print(writeblog(name='admin1'))
可以看到输入的用户是列表中没有的,因此需要登陆后方可执行writeblog函数
五 装饰器判断变量的数据类型
需求:判断数据类型装饰器required_ints
确保函数接收到的每一个参数都是整形数
如果不是整形数,就报错
import functools
def required_ints(fun):
@functools.wraps(fun)
def wrapper(*args, **kwargs): #args=1,4
for i in args: #i=1
if not isinstance(i, int):
# isinstance一次只会判断一个i值,如果判断是的话,就执行fun,
# 就会让后面的i,还没有判断,因此判断不是整形类型
print('函数类型不完全正确')
break
else:
res = fun(*args, **kwargs)
return res
return wrapper
@required_ints
def add(a, b):
return a + b
@required_ints
def my_max(a, b, c, d):
return max(a, b, c, d)
print(add(1, 4))
因输入的是两个整形,因此函数可以执行,且输出结果为5
六 带有多个装饰器的函数
需求1:用户登陆验证装饰器is_login
1)如果用户登陆成功,则执行被装饰的函数
2)如果用户未登陆,则执行登陆函数
需求2:判断用户是否为管理员,装饰器is_admin
1)如果是用户为管理员,则执行被装饰的函数
2)如果是用户不是管理员,则报错
import functools
login_users = ['admin', 'root']
def is_admin(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
if kwargs.get('name') == 'admin':
res=fun(*args,**kwargs)
return res
else:
return 'error'
return wrapper
def is_login(fun): # fun:writeblog
@functools.wraps(fun)
def wrapper(*args, **kwargs): # name='admin' #kwargs={name:'admin'}
# 判断写博客的这个用户是否登陆成功
if kwargs.get('name') in login_users:
res = fun(*args, **kwargs)
else:
res = login()
return res
return wrapper
# 必须登陆成功
@is_login # writeblog=is_login(writeblog)
@is_admin
def writeblog(name):
return 'write blog'
def login():
return 'login'
# 是否登陆都可以执行的函数
def news():
print('news')
print(writeblog(name='root'))
就算为root用户,也是不可以,除非是admin用户,方才可以
那么含有多个装饰器时,它的运行过程又是怎样的
def make1(fun):
print('blod1')
def wrapper1(*args, **kwargs):
print('blod2')
return fun(*args, **kwargs)
return wrapper1
def make2(fun):
print('m1')
def wrapper(*args, **kwargs):
print('m2')
return fun(*args, **kwargs)
return wrapper
@make1 # login=make1(login) #login为wrapper1
@make2 # login=make2(login) #login为wrapper
def login():
return '登陆'
print(login())
可以看到运行结果是依次打印的是 m1 blod1 blod2 m2 以及 登陆
也就是说:
当有多个装饰器时,从下到上调用装饰器
真实wrapper内容时从上到下执行的
七 带有参数的装饰器
编写装饰器,required_types
当装饰器为@required_types(int,float)时,确保函数接收到的每一个参数int或者float类型
当装饰器为@required_types(list)时,确保函数接收到的每一个参数list类型
当装饰器为@required_types(int,str)时,确保函数接收到的每一个参数int或者str类型
如果参数不满足,就报错,参数必须为xxx类型
import functools
import random
def required_types(*kinds):
def required(fun):
@functools.wraps(fun)
def wrapper(*args, **kwargs): #args=1,4
for i in args: #i=1
if not isinstance(i, kinds):
print('erorr,函数类型必须为',kinds)
break
else:
res = fun(*args, **kwargs)
return res
return wrapper
return required
@required_types(int,float)
def add(a, b):
return a + b
print(add(1,2.0))
因参数满足条件,因此输出结果为 3.0
八 动图处理
import random
from PIL import Image, ImageSequence
with Image.open('cat.gif') as im:
print(im.is_animated, im.n_frames)
# 判断图片是否为动图
if im.is_animated:
# 列表生成式
frames = [f.copy() for f in ImageSequence.Iterator(im)]
# 对列表反转
# frames = frames[::-1] #两者都可以
# frames.reverse()
# 对于动图的帧列表元素打乱顺序
random.shuffle(frames)
frames[0].save('out.gif', save_all=True, append_images=frames[1:])
#功能: 将动图的每一帧保存为一个图片;
index = 1
for frame in ImageSequence.Iterator(im):
print("image %d, mode: %s, size:%s" %(index, frame.mode, frame.size))
frame.save('img/frame%d.png' %(index))
index += 1
可以看到被打乱的gif图片被保存至了img目录下面