装饰器和有参装饰器
*****装饰器攻坚前置任务
# 1. *args, **kwargs
def index(x, y):
print(x, y)
def wrapper(*args, **kwargs):
index(*args, **kwargs) # 1.wrapper传的实参是什么形式会原封不动的传到index中 2.wrapper传参受限于index定义阶段形参的规则
wrapper(1, 2)
# 2. 名称空间与作用域: 名称空间的'嵌套'关系实在函数定义阶段产生的, 也可以说是检测语法的受确定的.
# 3. 函数对象: 可以把函数当作参数传入. 可以把函数当作返回值返回.
def index():
return 123
def bar(func):
func()
bar(index)
# 4. 函数嵌套定义
def outer():
def wrapper(): # wrapper在函数outer内定义, 把wrapper当作返回值返回到全局
pass
return wrapper
# 5. 闭包函数
def outer(x): # 把值x包给wrapper函数
# x = 1
def wrapper():
print(x)
return wrapper
f = outer(1)
f()
什么是装饰器
- '装饰': 代指被装饰对象添加新功能
- '器': 代指器具/工具.
可以理解为定义一个函数 合到一起
总结(重点): 装饰器指的就是定义一个函数, 用来为其他函数添加新功能.
为何要用装饰器?
软件的设计原则
软件的设计应该遵守开发封闭原则,即对扩展时开放的,对修改是封闭的.
- 对扩展开放: 对新的需求或变化, 可以对现有代码进行扩展开放.
- 对修改封闭: 对象设计完成, 就可以独立完成其工作, 对修改原代码封闭.
总结(重点): 开放: 拓展新功能开放 封闭: 修改源代码封闭
装饰器的作用(重点): 不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加新功能.
使用装饰器的原因:
- 软件包含的所有功能的源代码和调用方式, 都应该避免被修改, 否者一旦改错, 极有可能产生连锁反应, 最终导致程序奔溃.
- 上线后的软件, 新需求或者变化层出不穷, 我们必须提供扩展的可能性
装饰器的实现: 函数嵌套 + 函数对象 + 闭包函数
- 补充: time模块下的 time.time()功能
import time print(time.time()) # time.time()返回的值是时间戳. 以1970年作为起始, 到现在时间为结束. 单位秒 # 历史补充: 1970年是unix元年(10.25)
无参装饰器的实现 # 需求: 不修改index源代码和调用方式的前提下为index添加统计时间的功能
import time def foo(x): print("其实我这里有很多代码,但是你看不到") time.sleep(3) return x # 1、我们直接在函数内部进行更改。 def foo(x): start_time = time.time() print("其实我这里有很多代码,但是你看不到") time.sleep(3) stop_time = time.time() process_time = stop_time - start_time print(process_time) return x
这时候虽然我们增加了功能,但是我们已经违反了开放封闭原则,所以失败。
# 2、利用函数的嵌套定义来直接增加新功能 def outer(x): start_time = time.time() foo(x) stop_time = time.time() process_time = stop_time - start_time print(process_time) return x
我们同样实现了功能,调用方式改变,源代码没变。
# 3、利用闭包函数来修改 def outer(foo): def inter(x): start_time = time.time() res = foo(x) stop_time = time.time() process_time = stop_time - start_time print(process_time) return res return inter foo = outer(foo) foo(x)
上述已经实现了不更改函数源码,不更改调用方式了,但是函数被写死了
如果用来修饰多个函数呢?这些函数又有不同个数的参数呢
# 终极版装饰器的诞生。 def outer(func): def inter(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) stop_time = time.time() process_time = stop_time - start_time print(process_time) return res return inter foo = outer(foo)
这是利用星号在形参和实参不同的作用来实现的。
自此,装饰器就写好了,遵守开放封闭原则,让用户在不知情的情况下,完成了函数的更新。
要想掌握装饰器,必须弄懂闭包函数这些前置只是,如果还是不太懂,就继续看看闭包函数板块和参数的知识。
def outer(func): def inter(*args,**kwargs) # 调用源代码 # 为其增加的新功能 res = func(*args,**kwargs) return res return inter
添加装饰器的时候,可以根据模板然后在inter函数里面添加相关功能。
当然,还有一点就是语法糖的使用,我们可以将装饰器的赋值转成在被修饰函数上一行添加@装饰器名。
# 语法糖:在被修饰函数上一行添加@装饰器名。 @outer def foo(x): print(x)
有参装饰器
由于语法糖@的限制,outter函数只能有一个参数,并且该函数只用来接收被装饰对象的内存地址(也就是两层不够用,需要再来一层进行传参)
山炮玩法:不使用语法糖,第二层传参也能实现功能 例如装饰index函数 index=auth(index,'file')
有参装饰器模板: def 有参装饰器(x,y,z): def outter(func): def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper return outter
- 偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数
- 所以应该将wrapper做的跟原函数一样才行
from functools import wraps
__name__, __doc__等装饰的一毛一样
@wraps(func) 去装饰我们偷梁换柱后的对象
# 手动将原函数的属性赋值给wrapper函数 # 1、函数wrapper.__name__ = 原函数.__name__ # 2、函数wrapper.__doc__ = 原函数.__doc__ # wrapper.__name__ = func.__name__ # wrapper.__doc__ = func.__doc__ return wrapper
山炮玩法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 # @auth # 账号密码的来源是文件 def index(x,y): print('index->>%s:%s' %(x,y)) # @auth # 账号密码的来源是数据库 def home(name): print('home->>%s' %name) # @auth # 账号密码的来源是ldap def transfer(): print('transfer') index=auth(index,'file') home=auth(home,'mysql') transfer=auth(transfer,'ldap') # index(1,2) # home('egon') # transfer()
山炮玩法2:
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()