一、装饰器
在不改变原函数的基础上,给函数增加功能,把一个函数当作参数,返回一个替代版的函数
原则:1、不能修改被装饰 函数的源代码;
2、不能修改被装饰的函数的调用方式
本质上:一个返回函数的函数
二、装饰器的举例
1.要实现在我调用的函数得到的输出结果之前和之后打印两行“*“号
# _*_ coding:utf-8 _*_
def func1():
print 'have a good time'
def func2():
print 'good luck'
def outer(func):
def inner():
print '**********'
func()
print '##########'
return inner
# outer(func1)
# outer(func2)
func1 = outer(func1)
func1()
"""结果如下"""
# **********
# have a good time
# **********
上面的outer函数就是一个装饰器,func为形参接受一个函数,inner为返回的函数。即经过装饰的函数。
2.模拟银行ATM显示:针对不同的节日ATM机的显示应该是不同的,即可以改变;但是其取钱,存钱的操作是不能改变的,故我们可以写一个装饰器来装扮ATM机的显示。
# _*_ coding:utf-8 _*_
def desc(func):
def inner():
print '中秋快乐'
func()
print 'python is good'
return inner
@desc # 语法糖 要使用装饰器的时候,直接在函数的上面加上@desc
def func1():
print '欢迎使用...'
@desc
def func2():
print '取钱...'
@desc
def func3():
print '存钱...'
# @desc == func1 = desc(func1)
func1()
func2()
func3()
"""结果如下"""
# 中秋快乐
# 欢迎使用...
# 广告1...
# 中秋快乐
# 取钱...
# 广告1...
# 中秋快乐
# 存钱...
# 广告1...
语法糖:@ + 装饰器函数名 (@desc)
使用:若要使用装饰器来装饰一个函数,只需要在函数的开头加上@+装饰器函数名即可。
实质:@ + 装饰器函数名 == 被装饰函数 = desc(被装饰函数)
例2中如若现在是春节,广告也换为了广告2,我们只需要改装饰器的内容即可,如下代码所示。
def desc(func):
def inner():
print '新春快乐'
func()
print '广告2...'
return inner
三、高级通用装饰器
1.计算一个函数执行的时间
# _*_ coding:utf-8 _*_
import time
import random
import string
import functools
# 列表生成式,通过使用导入的random库和string库的方法,来生成有1000个以大写或者小写字母为元素组成的列表
li = [random.choice(string.ascii_letters) for i in range(1000)]
def timeit(fun):
"""计算函数执行时间的装饰器函数"""
@functools.wraps(fun)
def wrapper(*args, **kwargs):
start_time = time.time()
res = fun(*args, **kwargs)
end_time = time.time()
print '运行时间为:%.6f' % (end_time - start_time)
return res
return wrapper
@timeit
def con_add():
s = ''
for i in li:
s += (i + ',')
@timeit
def join_add():
','.join(li)
@timeit
def fun_list(n):
[2 * i for i in range(n)]
@timeit
def fun_map(n):
list(map(lambda x: x * 2, range(n)))
print con_add()
join_add()
fun_list(50000)
fun_map(50000)
"""结果如下"""
# 运行时间为:0.000231
# 运行时间为:0.000023
# 运行时间为:0.006882
# 运行时间为:0.012010
2.打印日志信息
# _*_ coding:utf-8 _*_
import functools
import time
def add_log(fun):
@functools.wraps(fun)
def inner(*args, **kwargs):
start_time = time.time()
res = fun(*args, **kwargs)
end_time = time.time()
print '日志时间:%s 函数名:%s 运行时间:%.6f 运行结果:%d' \
% (time.ctime(), fun.__name__, end_time - start_time, res)
return res
return inner
@add_log
def add(x, y):
time.sleep(1)
return x + y
add(3, 4)
"""结果如下"""
# 日志时间:Thu Sep 6 10:28:11 2018 函数名:add 运行时间:1.000220 运行结果:7
补充:函数的属性
__name__ 属性:用来记录函数的名称
__doc__ 属性:用来记录函数的文档字符串
3.用户验证登陆
需求1:用户登陆验证的装饰器is_login
1).如果用户登陆成功,则执行被装饰的函数
2).如果用户登陆不成功,则执行登陆函数
需求2:判断登陆用户是否为管理员is_admin
1).是,执行装饰函数
2).不是,则报错
# _*_ coding:utf-8 _*_
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):
@functools.wraps(fun)
def wrapper(*args, **kwargs):
if kwargs.get('name') in login_users:
res = fun(*args, **kwargs)
return res
else:
res = login()
return res
return wrapper
@is_login
@is_admin
def writeBlog(name):
return '编写博客'
def login():
return '登陆。。。'
print writeBlog(name='root')
"""结果如下"""
# Error,没有权限
print writeBlog(name='admin')
"""结果如下"""
# 编写博客
小结:
一个函数的装饰器可以有多个,如要使用多个装饰器,在函数的定义之前加上对应装饰器的语法糖即可。
多个装饰器执行顺序的说明:简言之从下向上调用,从上到下执行(详细解读可参考博文《探究多个装饰器执行顺序》)
4.判断函数所跟参数是否合法
# _*_ coding:utf-8 _*_
import functools
def select_types(*kind):
def required_types(fun):
@functools.wraps(fun)
def wrapper(*args, **kwargs):
for i in args:
if isinstance(i, kind):
pass
else:
print 'TypeError:参数必须为', kind
break
else:
res = fun(*args, **kwargs)
return res
return wrapper
return required_types
@select_types(int, float)
def add(a, b):
return a + b
@select_types(list)
def li(*args):
return args
@select_types(str, int)
def li1(a, b):
return a, b
print add(12, 1.2)
print add(12, '1')
print li([1, 2, 3])
print li1('str', 1)
"""结果如下"""
# 参数合法
# 13.2
# 参数不合法:参数必须为 (<type 'int'>, <type 'float'>)
# None
# 参数合法
# ([1, 2, 3],)
# 参数合法
# ('str', 1)
总结:
函数装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用,提高了工作的效率。