day 17 装饰器
1 无参装饰器
1.1 储备知识
1.1.1 *args, **kwargs
def index(x,y):
print(x,y)
def wrapper(*args,**kwargs):
index(*args,**kwargs)
wrapper(y=222,x=111) # index(y=222,x=111)
1.1.2 名称空间与作用域:
名称空间的的"嵌套"关系是在函数定义阶段,即检测语法的时候确定的
1.1.3 函数对象:
可以把函数当做参数传入
可以把函数当做返回值返回
def index():
return 123
def foo(func):
return func
foo(index)
1.1.4 函数的嵌套定义:
def outter(func):
def wrapper():
pass
return wrapper
1.1.5 闭包函数
def outter():
x=111
def wrapper():
print (x)
return wrapper#
f=outter()
1.1.6 传参的方式
- 传参的方式一:通过参数的形式为函数体传值
def wrapper(x):
print(1)
print(2)
print(3)
wrapper(1)
wrapper(2)
wrapper(3)
传参的方式二:通过闭包的方式为函数体传值
def outter(x):
def wrapper():
print(x)
return wrapper # return outter 内的 wrapper 那个函数的内地址
f1=outter(1)
f2=outter(2)
f3=outter(3)
wrapper=outter(1)
1.2 无参装饰器
1.2.1 什么是装饰器
'装饰'代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。
器指的是工具,可以定义成成函数。装饰指的是为其他事物添加额外的东西点缀
合到一起的解释:
装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能
1.2.2 为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
开放封闭原则
开放:指的是对拓展功能是开放的
封闭:指的是对修改源代码是封闭的
装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
1.2.3 装饰器推导过程
-
需求:在不修改 index 函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
def index(x,y): time.sleep(3) print('index %s %s' %(x,y)) index(111,222) -
解决方案一:失败
问题:没有修改被装饰对象的调用方式,但是修改了其源代码
import time def index(x,y): start=time.time() time.sleep(3) print('index %s %s' %(x,y)) stop = time.time() print(stop - start) index(111,222) -
解决方案二:失败
问题:没有修改被装饰对象的调用方式,也没有修改了其源代码,并且加上了新功能,但是代码冗余
import time def index(x,y): time.sleep(3) print('index %s %s' %(x,y)) start=time.time() index(111,222) stop=time.time() print(stop - start) start=time.time() index(111,222) stop=time.time() print(stop - start) start=time.time() index(111,222) stop=time.time() print(stop - start) -
解决方案三:失败
问题:解决了方案二代码冗余问题,但带来一个新问题即函数的调用方式改变了
import time def index(x,y): time.sleep(3) print('index %s %s' %(x,y)) def wrapper(): start=time.time() index(111,222) stop=time.time() print(stop - start) wrapper() -
方案三的优化一:将 index 的参数写活了
import time def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) def wrapper(*args,**kwargs): start=time.time() index(*args,**kwargs) # index(3333,z=5555,y=44444) stop=time.time() print(stop - start) wrapper(3333,4444,5555) wrapper(3333,z=5555,y=44444) -
方案三的优化二:在优化一的基础上把被装饰对象写活了,原来只能装饰 index
import time def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) def home(name): time.sleep(2) print('welcome %s to home page' %name) def outter(func): def wrapper(*args,**kwargs): start=time.time() func(*args,**kwargs) # index 的内存地址() stop=time.time() print(stop - start) return wrapper index=outter(index) # index=wrapper 的内存地址 home=outter(home) # home=wrapper 的内存地址 home( 'egon' ) -
方案三的优化三:将 wrapper 做的跟被装饰对象一模一样,以假乱真
import time def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) def home(name): time.sleep(2) print('welcome %s to home page' %name) def outter(func): def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print(stop - start) return res return wrapper home=outter(home) res=home('egon') # res=wrapper('egon') print('返回值',res) -
语法糖:让你开心的语法
import time def timmer(func): def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print(stop - start) return res return wrapper # 在被装饰对象正上方的单独一行写@装饰器名字 @timmer >>>> index=timmer(index) def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) @timmer >>>> home=timmer(ome) def home(name): time.sleep(2) print('welcome %s to home page' %name) index(x=1,y=2,z=3) home('egon') -
思考题(选做),叠加多个装饰器,加载顺序与运行顺序
@deco1 # index=deco1(deco2.wrapper 的内存地址) @deco2 # deco2.wrapper 的内存地址=deco2(deco3.wrapper 的内存地址) @deco3 # deco3.wrapper 的内存地址=deco3(index) def index(): pass
1.2.4 总结无参装饰器模板
-
模板
def outter(func): def wrapper(*args,**kwargs): # 1 调用原函数 # 2 为其增加新功能 res=func(*args,**kwargs) return res return wrapper
def auth(func): # 登录装饰器
def wrapper(*args,**kwargs):
name=input('your name>>: ').strip()
pwd=input('your password>>: ').strip()
if name == 'egon' and pwd == '123':
res=func(*args,**kwargs)
return res
else:
print('账号密码错误')
return wrapper
return wrapper
@auth
def index():
print('from index')
index()
写装饰器三步走
1,先将装饰功能写出来
2,包装成为函数
3,将参数,函数写活
-------------------------------------------
把装饰器看做一个加工厂
装饰器就是原函数进来经过加工(装饰)之后返回一个外表相同内在不同的新函数
所以装饰器的模板有两层函数:
1:输入输出函数
2:加工函数
2 有参装饰器
2.1 知识储备
由于语法糖@的限制,outter 函数只能有一个参数,并且该才是只用来接收被装饰对象的内存地址
def outter(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
@outter #index=outter(index) index=>wrapper
def index(x,y):
print(x,y)
-
偷梁换柱之后
index 的参数什么样子,wrapper 的参数就应该什么样子 index 的返回值什么样子,wrapper 的返回值就应该什么样子 index 的属性什么样子,wrapper 的属性就应该什么样子 ==》from functools import wraps
2.2 有参装饰器
-
山炮 1:
def auth(func, db_type): def wrapper(*args, **kwargs): name=input('your name>>>: ').strip() pwd=input('your password>>>: ').strip() if db_type == 'file': print('基于文件的验证') if name == 'egon' and pwd == '123': res = func(*args, **kwargs) return res else: print('user or password error') elif db_type == 'mysql': print('基于 mysql 的验证') elif db_type == 'ldap': print('基于 ldap 的验证') else: print('不支持该 db_type') return wrapper def index(x,y): print('index->>%s:%s' %(x,y)) def home(name): print('home->>%s' %name) def transfer(): print('transfer') index=auth(index,'file') home=auth(home,'mysql') transfer=auth(transfer,'ldap') index(1,2) home('egon') transfer() -
山炮 2
def auth(db_type): def deco(func): def wrapper(*args, **kwargs): name=input('your name>>>: ').strip() pwd=input('your password>>>: ').strip() if db_type == 'file': print('基于文件的验证') if name == 'egon' and pwd == '123': res = func(*args, **kwargs) return res else: print('user or password error') elif db_type == 'mysql': print('基于 mysql 的验证') elif db_type == 'ldap': print('基于 ldap 的验证') else: print('不支持该 db_type') return wrapper return deco deco=auth(db_type='file') @deco #账号密码的来源是文件 def index(x,y): print('index->>%s:%s' %(x,y)) deco=auth(db_type='mysql') @deco #账号密码的来源是数据库 def home(name): print('home->>%s' %name) deco=auth(db_type='ldap') @deco #账号密码的来源是 ldap def transfer(): print('transfer') index(1,2) home('egon') transfer() -
最终 语法糖
def auth(db_type): def deco(func): def wrapper(*args, **kwargs): name = input('your name>>>: ').strip() pwd = input('your password>>>: ').strip() if db_type == 'file': print('基于文件的验证') if name == 'egon' and pwd == '123': res = func(*args, **kwargs) # index(1,2) return res else: print('user or password error') elif db_type == 'mysql': print('基于 mysql 的验证') elif db_type == 'ldap': print('基于 ldap 的验证') else: print('不支持该 db_type') return wrapper return deco @auth(db_type='file') >>@deco >>index=deco(index) >>index=wrapper def index(x, y): print('index->>%s:%s' % (x, y)) @auth(db_type='mysql') # @deco # home=deco(home) # home=wrapper def home(name): print('home->>%s' % name) @auth(db_type='ldap') # 账号密码的来源是 ldap def transfer(): print('transfer') index(1, 2) home('egon') transfer()
2.3 有参装饰器模板
有参装饰器就是给无参装饰器包了一个包
def 有参装饰器(x,y,z):
def outter(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
return res
return wrapper
return outter
@有参装饰器(1,y=2,z=3)
def 被装饰对象():
pass
3 补充
偷梁换柱,即将原函数名指向的内存地址偷梁换柱成 wrapper 函数所以应该将 wrapper 做的跟原函数一样才行
手动将原函数的属性赋值给 wrapper 函数
1、函数 wrapper.__ name__ = 原函数.__ name__
2、函数 wrapper.__ doc __ = 原函数. __ doc __
from functools import wraps
def outter(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""这个是主页功能"""
res = func(*args, **kwargs) # res=index(1,2)
return res
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
@outter # index=outter(index)
def index(x,y):
"""这个是主页功能"""
print(x,y)
print(index.__name__)
print(index.__doc__) #help(index)
index(1,2) # wrapper(1,2)
4 叠加多个装饰器
4.1 叠加多个装饰器的加载、运行分析
def deco1(func1): # func1 = wrapper2 的内存地址
def wrapper1(*args,**kwargs):
print('正在运行===>deco1.wrapper1')
res1=func1(*args,**kwargs)
return res1
return wrapper1
def deco2(func2): # func2 = wrapper3 的内存地址
def wrapper2(*args,**kwargs):
print('正在运行===>deco2.wrapper2')
res2=func2(*args,**kwargs)
return res2
return wrapper2
def deco3(x):
def outter3(func3): # func3=被装饰对象 index 函数的内存地址
def wrapper3(*args,**kwargs):
print('正在运行===>deco3.outter3.wrapper3')
res3=func3(*args,**kwargs)
return res3
return wrapper3
return outter3
4.2 加载顺序自下而上
@deco1 >>> index=deco1(wrapper2 的内存地址) ===> index=wrapper1 的内存地址
@deco2 >>>index=deco2(wrapper3 的内存地址) ===> index=wrapper2 的内存地址
@deco3(111) ===>@outter3===> index=outter3(index) ===> index=wrapper3 的内存地址
def index(x,y):
print('from index %s:%s' %(x,y))
# 执行顺序自上而下的,即 wraper1-》wrapper2-》wrapper3
index(1,2) # wrapper1(1,2)
======================wrap1
==================wrap2
==============wrap3
==========index
==============wrap3
==================wrap2
======================wrap1
叠加多个装饰器就跟套饼一样
装饰的时候是自下而上,由里而外。
运行时自上而下,由外至里。

浙公网安备 33010602011771号