1 装饰器
理解装饰器需要理解:作用域、高阶函数、闭包
1.1 闭包
定义:内部函数对外部函数的环境进行引用,内部函数叫做闭包(closer)。
一般应用的时候外部函数的返回值为内部函数名。
闭包可以保存函数的运行环境
def ouer()
x = 1
def inner()
print(x)
return inner
其中inner就叫做闭包
1.2 基本装饰器
装饰器本身是个函数,返回值为函数对象,参数为函数对象。
作用:为已存在的业务在不改变其原代码的情况下添加新的功能,且不用改变名字。
例如:
在原有的foo()业务上需要增加运行时间输出的功能,需要不改变原有代码和调用逻辑。
按照需求,必须保证在将新函数show_time()赋值给原来的foo函数的时候保证foo本身的环境还存在,因此需要将foo作为参数传入到show_time()执行,而show_time()内部还有函数去执行这个父级函数的变量,内部函数形成闭包:
'''简单装饰器'''
#原有业务代码
def foo():
print('原有业务')
time.sleep(3)
# 增加功能
def show_time(func):
def wrapper():
tm1 = time.time()
func()
tm2 = time.time()
print('the run time is: %s' % (tm2-tm1))
return wrapper
foo = show_time(foo)
foo()
标准写法:
'''标准装饰器写法'''
#装饰器内容
def show_time(func):
def wrapper():
tm1 = time.time()
func()
tm2 = time.time()
print('the run time is: %s' % (tm2-tm1))
return wrapper
#在所有业务前面增加@show_time
@show_time #等价于 foo = show_time(foo)
def foo():
print('原有业务')
time.sleep(3)
foo()
1.3 带参数的被装饰函数
区别不大,就是内部函数时刻与被装饰函数保证一致;
- 形参保持一致
- 返回值保持一致
1.3.1 形参保持一致案例
外部函数只是为了将被装饰函数作为参数传入形成外部环境从而造成内部函数为闭包。
内部函数才是实现功能和与被装饰函数保持一致的关键。
'''带参数的内部函数(必须参数)'''
#装饰器内容
def show_time(func):
def wrapper(x, y):
tm1 = time.time()
func(x, y)
tm2 = time.time()
print('the run time is: %s' % (tm2-tm1))
return wrapper
#在所有业务前面增加@show_time
@show_time #等价于 foo = show_time(foo)
def foo(x, y):
print('原有业务为%s+%s=%s' % (x, y, x+y))
time.sleep(3)
foo(2, 3)
'''带参数的内部函数(不定长参数)'''
#装饰器内容
def show_time(func):
def wrapper(*args, **kwargs):
tm1 = time.time()
func(*args, **kwargs)
tm2 = time.time()
print('the run time is: %s' % (tm2-tm1))
return wrapper
#在所有业务前面增加@show_time
@show_time #等价于 foo = show_time(foo)
def foo(*args, **kwargs):
print('原有业务为')
print(args, kwargs)
time.sleep(3)
foo(2, 3, name='zhao', age=13)
foo(*[1, 2, 3], **{'name': 'ren', 'age': 78})
1.3.2 返回值保持一致案例
如果返回值不一致会导致装饰后的函数没有返回值
import time
def show_time(func):
def wrapper(a,b):
start_time=time.time()
ret=func(a,b)
end_time=time.time()
print('spend %s'%(end_time-start_time))
return ret
return wrapper
@show_time #add=show_time(add)
def add(a,b):
time.sleep(1)
return a+b
print(add(2,5))
注意点
1.4 装饰函数加参数
其实很好理解,就是在装饰函数参数中加入缺省参数,如果不采用官方做法利用@这种语法糖的话,可以按照以下操作实现。
例如:提供接口,可以实现设置是否显示运行时间:
'''装饰函数加参数:非官方写法,用于理解'''
#装饰器内容
def show_time(func, flag=True):
def wrapper(*args, **kwargs):
tm1 = time.time()
ret = func(*args, **kwargs)
tm2 = time.time()
if flag:
print('the run time is: %s' % (tm2-tm1))
return ret
return wrapper
#业务函数,有返回值
def foo(*args, **kwargs):
print('原有业务为')
print(args, kwargs)
time.sleep(3)
return 0
# foo = show_time(foo)
# foo(2, 3, name='zhao', age=13)
foo = show_time(foo, False)
foo(*[1, 2, 3], **{'name': 'ren', 'age': 78})
问题:以上代码中被隐掉的部分如果放开,第二次调用传入的参数不起作用了,单步调试时候时候发现当第二次启用foo()的时候进入装饰器内部函数执行到func()的时候又弹到上一步并使得传入的flag又False变为True
官方用@语法糖写法:
'''官方语法糖写法'''
#装饰器内容(再封装一层带参数的的外部函数)
def flag(flag=True):
def show_time(func):
def wrapper(*args, **kwargs):
tm1 = time.time()
ret = func(*args, **kwargs)
tm2 = time.time()
if flag:
print('the run time is: %s' % (tm2-tm1))
return ret
return wrapper
return show_time
@flag(False) #等价于 foo = flag(False)(foo)
#@flag(True) #等价于 foo = flag(True)(foo)
def foo(*args, **kwargs):
print('原有业务为')
print(args, kwargs)
time.sleep(3)
return 0
#foo = flag(False)(foo)
#foo = flag(True)(foo)
foo(2, 3, name='zhao', age=13)
foo(*[1, 2, 3], **{'name': 'ren', 'age': 78})
1.5 多层装饰器
def makebold(fn):
def wrapper():
return "<b>" + fn() + "</b>"
return wrapper
def makeitalic(fn):
def wrapper():
return "<i>" + fn() + "</i>"
return wrapper
@makebold
@makeitalic
def hello():
return "hello alvin"
hello()
2 装饰器应用之登陆
需求: 类比京东界面,有京东金融、家界面、京东图书等原有的业务函数。现将这三类业务保证源代码的基础上增加登陆功能,要求其中任何一个界面登陆过其他页面不需要再执行登陆:
isloged = False
def login(func):
def wrapper():
global isloged
if isloged:
ret = func()
pass
else:
ret = func()
name = input('name:')
passwd = input('passwd:')
with open('info', 'r') as f:
dic_base = eval(f.read())
if (name in dic_base) and dic_base[name] == passwd:
print('welcom....')
isloged = True
else:
print('you are wrong...')
return ret
return wrapper
@login
def jd_home():
print('this is home')
return 0
@login
def jd_book():
print('this is book')
return 0
@login
def jd_finance():
print('this is finance')
return 0
if __name__ == '__main__':
while True:
jd_book()
jd_finance()
jd_home()
#info中
{'zhao': '123', 'xu': '234'}
```