一、開放封閉原則
不改變調用方式與源代碼的前提下增加功能
- 不能修改被裝飾對象(函數)的源代碼
- 不能修改被修飾對象(函數)的調用方式,且能達到增加功能的效果(開放)
二、初識
洗碗機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)