一、开放封闭原则
不改变调用方式与源代码的前提下增加功能
- 不能修改被装饰对象(函数)的源代码
- 不能修改被修饰对象(函数)的调用方式,且能达到增加功能的效果(开放)
二、初识
洗碗机Dishwasher
初级功能——洗碗
def washer():
print('洗碗')
washer()
为洗碗机增加消毒功能
方法一:
def washer():
print('洗碗')
print('消毒')
washer()
不满足开放封闭原则,修改了被装饰对象(函数)的源代码
方法二:
def washer():
print('洗碗')
def wrap(fn):
fn()
print('消毒')
wrap(washer)
不满足开放封闭原则,修改了被修饰对象(函数)的调用方式
方法三:
def washer():
print('洗碗')
def fn():
washer()
print('消毒')
washer = fn
washer()
满足了开放封闭原则,但是出现了函数调用的死循环
方法四:
def washer():
print('洗碗')
tag = washer
def fn():
tag()
print('消毒')
washer = fn
washer()
满足开放封闭原则,且可以达到拓展功能的目的,但是tag暴露在全局,容易被修改。
方法五:
def washer():
print('洗碗')
下面的函数嵌套结构就是函数装饰器
def wrap(tag):
def fn():
tag() # 原来的洗碗功能washer()
print('消毒')
return fn # 拓展功能后的washer()
washer = wrap(washer) # 增加功能后的washer()重新赋值给washer
washer()
- 把要被装饰的函数作为外层函数的参数通过闭包操作后返回一个替代版函数
- 被装饰的函数:fn
- 外层函数:outer(func) outer(fn) => func = fn
- 替代版函数: return inner: 原功能+新功能
装饰器模板
def fn():
print("原有功能")
# 装饰器
def outer(tag):
def inner():
tag()
print(新增功能")
return inner
fn = outer(fn)
fn()
三、函数装饰器简化语法
def disinfect(fn):
def inner():
fn()
print('消毒')
return inner
def dry(fn):
def inner():
fn()
print('烘干')
return inner
#语法糖 | 笑笑语法
@disinfect
@dry
def washer():
print('洗碗')
washer() # 洗碗
烘干
消毒
简化模板
def outer(f):
def inner():
f()
print("新增功能1")
return inner
def outer2(f):
def inner():
f()
print("新增功能2")
return inner
@outer2 # 被装饰的顺序决定了新增功能的执行顺序
@outer # fn = outer(fn): inner
def fn():
print("原有功能")
调用函数装饰器顺序的不同会影响函数的运行顺序,例如先@dry再@disinfect显示结果将是洗碗、消毒、烘干。越靠近原函数越早运行。
四、有参数有返回值的函数被修饰
#账号处理功能:3位及以上英文字母或汉字
def check_usr(fn): # fn, login, inner:不同状态下的login,所以参数是统一的
def inner(usr,pwd):
if not (len(usr) >= 3 and usr.isalpha()):
print('账号登录失败')
return False
result = fn(usr,pwd)
return result
return inner
#密码处理功能:6位及以上英文和数字
def check_pwd(fn):
def inner(usr,pwd):
if not (len(pwd) >= 6 and pwd.isalnum()):
print('密码验证失败')
return False
result = fn(usr,pwd)
return result
return inner
@check_usr
@check_pwd
# 登录功能
def login(usr,pwd):
if usr == 'abc' and pwd == '123456':
print('登录成功')
return True
print('登录失败')
return False
res = login('ab','123')
print(res)
总结:
- login有参数,所以inner与fn都有相同参数
- login有返回值,所以inner与fn都有返回值 针对无法确定参数数目的情况,我们用*args,**kwargs一举包揽。
函数装饰器最终写法
def wrap(fn):
def inner(*args, **kwargs):
print('前增功能')
result = fn(*args, **kwargs)
print('后增功能')
return result
return inner
@wrap
def fn1():
print('fn1的原有功能')
@wrap
def fn2(a, b):
print('fn2的原有功能')
@wrap
def fn3():
print('fn3的原有功能')
return True
@wrap
def fn4(a, *, x):
print('fn4的原有功能')
return True
fn1()
fn2(10, 20)
fn3()
fn4(10, x=20)