工廠函數
普通工廠函數的實現def recoder(strname, age):n print('name:', strname, 'age:', age)nndef cuifun(age):
普通工廠函數的實現
def recoder(strname, age):n print('name:', strname, 'age:', age)nndef cuifun(age): # 工廠函數n strname='cui'n return recoder(strname,age)nncuifun(24) # name: cui age: 24
工廠函數cuifun封裝了recoder,但是這樣寫很麻煩,每換一個strname都要寫不同的函數
閉合函數(closure)
def wrapperfun(strname): # 閉合函數,strname為自由變量n def recoder(age):n print("name:", strname, 'age:', age)n return recodernnfun = wrapperfun('cui') # fun是recoder的閉合函數nfun(24) # name: cui age: 24nnfun2 = wrapperfun('ma')nfun2(25) # name: ma age: 25nn# __closure__屬性記錄自由變量的參數對象地址(tuple),當閉合函數被調用時,會根據該地址找到自由變量nprint(fun.__closure__) # (<cell at 0x00000153E6C59A98: str object at 0x00000153E6D7C8F0>,)
實現裝飾器
在wrapperfun函數上增加參數校驗的功能:
def checkParam(fn): # 裝飾器函數n def wrapper(strname):n if isinstance(strname, (str)):n return fn(strname)n print("variable strname is not a string type")n returnnn return wrappernnndef wrapperfun(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernnnwrapperfun2 = checkParam(wrapperfun)nfun = wrapperfun2('cui')nfun(24) # name: cui age: 24nfun = wrapperfun2(37) # variable strname is not a string type
checkParam是一個裝飾器函數,返回值是內部定義的wrapper函數。為了實現對原有函數wrapperfun的參數檢查,將wrapper函數的參數和wrapperfun的參數保持一致。若被檢查的參數合法,再調用原有函數,并將參數傳進去;若不合法,則打印警告。
在使用時,直接將wrapperfun傳入checkParam中,來完成對原函數wrapperfun的裝飾,并得到帶有參數檢查的閉合函數wrapperfun2。
裝飾器本質是一個閉合函數,該閉合函數的自由變量是一個函數。
@修飾符
def checkParam(fn):n def wrapper(strname):n if isinstance(strname, (str)):n return fn(strname)n print("variable strname is not a string type")n returnnn return wrappernn@checkParamndef wrapperfun(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernnfun = wrapperfun('cui')nfun(24) # name: cui age: 24nnfun = wrapperfun(37) # variable strname is not a string type
能夠接收任何參數的通用參數修飾器
之前的裝飾器checkParam內部實現了一個wrapper函數,wrapper函數的參數與被裝飾函數參數一樣,這樣才能實現對被裝飾函數的參數檢查,并將輸入參數傳給裝飾函數。
為了在定義裝飾器時解耦內部wrapper的參數對被裝飾函數的強依賴關系,可以使用能夠適應任何參數的通用參數裝飾器
def checkParam(fn): # 裝飾器n def wrapper(*args, **kwargs):n if isinstance(args[0], (str)): # 對fn的第一個參數的類型進行檢查n return fn(*args, **kwargs)n print("variable strname is not a string type")n returnnn return wrapper
可接收參數的通用修飾器
def isadmin(userid):n def checkParam(fn):n def wrapper(*args, **kwargs):n if userid != 'admin':n print('Operation is prohibited as you are not admin!')n returnn if isinstance(args[0], (str)):n return fn(*args, **kwargs)n print("variable strname is not a string type")n returnnn return wrappern return checkParamnn@isadmin(userid='admin')ndef wrapperfun(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernn@isadmin(userid='user')ndef wrapperfun2(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernnnfun = wrapperfun('cui') # 此時的fun是strname初始化后的recoder nfun(24) # name: cui age: 24nnfun2 = wrapperfun(37) # variable strname is not a string typenfun3 = wrapperfun2(37) # Operation is prohibited as you are not admin!nnn# 下面不用@寫一個普通裝飾器ndef wrapperfun3(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernn# 執行isadmin(userid='user')返回一個裝飾器checkParam,n# 再調用checkParam,傳入參數wrapperfun3,返回wrapper函數nwrapperfun3 = isadmin(userid='admin')(wrapperfun3)nprint(wrapperfun3.__name__) # wrappernfun4=wrapperfun3('cui')nfun4(24)
注意:使用@修飾符使調用時節省了不少代碼,可以直接調用被修飾函數了。
裝飾器返回函數的名稱修復
def isadmin(userid):n def checkParam(fn):n def wrapper(*args, **kwargs):n if userid != 'admin':n print('Operation is prohibited as you are not admin!')n returnn if isinstance(args[0], (str)):n return fn(*args, **kwargs)n print("variable strname is not a string type")n returnn wrapper.__name__ = fn.__name__ # 修改wrapper的namen return wrappern return checkParamnnnndef wrapperfun(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernnwrapperfun = isadmin(userid='admin')(wrapperfun)nprint(wrapperfun.__name__) # wrapperfun
也可以通過內置裝飾器functools.wraps來實現此功能
import functoolsndef isadmin(userid):n def checkParam(fn):n @functools.wraps(fn) # wrapper的函數名稱會變成與傳入的fn參數一樣的函數名稱n def wrapper(*args, **kwargs):n if userid != 'admin':n print('Operation is prohibited as you are not admin!')n returnn if isinstance(args[0], (str)):n return fn(*args, **kwargs)n print("variable strname is not a string type")n returnnn return wrappern return checkParamnnnndef wrapperfun(strname):n def recoder(age):n print('name:', strname, 'age:', age)nn return recodernnwrapperfun = isadmin(userid='admin')(wrapperfun)nprint(wrapperfun.__name__) # wrapperfun
組合裝飾器
def checkParam(fn):n def wrapper(*args, **kwargs):n if isinstance(args[0], (str)):n return fn(*args, **kwargs)n print("variable strname is not a string type")n returnn return wrappernndef logging(userid):n def checkParam(fn):n def wrapper(*args, **kwargs):n print(userid,end=':')n return fn(*args, **kwargs)n return wrappern return checkParamnn@logging(userid='admin')n@checkParamndef wrapperfun(strname):n def recoder(age):n print('name:',strname,'age:',age)n return recodernnfun = wrapperfun('cui')nfun(24) # admin:name: cui age: 24
使用裝飾器,可以將代碼按照要實現功能的主次逐層分開,這就是面向切面的編程思想(Aspect Oriented Program AOP)
多裝飾器的調用順序
def logging(fn):n print("in logging")n def wrapper_logging(*args, **kwargs):n print("in wrapper_logging")n return fn(*args, **kwargs)n return wrapper_loggingnndef checkParam(fn):n print("in checkParam")n def wrapper_checkParam(*args, **kwargs):n print('in wrapper_checkParam')n return fn(*args, **kwargs)n return wrapper_checkParamnn@loggingn@checkParamndef wrapperfun(strname): # 輸出:in checkParam n in loggingn print('name:',strname)nnwrapperfun('cui') # 輸出:in wrapper_logging n in wrapper_checkParam n name:cui
如果不使用@修飾器,代碼執行順序可以看的很清楚:
def wrapperfun(strname):n print('name:',strname)nnnc = checkParam(wrapperfun) # in checkParamnprint(c.__name__) # wrapper_checkParamnc2 = logging(c) # in loggingnprint(c2.__name__) # wrapper_loggingnc2('cui') # in wrapper_logging n in wrapper_checkParam n name:cui
解決‘同作用域下默認參數被覆蓋’的問題
def recoder(strname, age):n print('name:',strname,'age:',age)nndef makerecoders():n acts = []n for i in ["cui","ma"]:n acts.append(lambda age:recoder(i, age))n return actsnnfor a in (makerecoders()):n a(age=32) # name: ma age: 32 n name: ma age: 32
上面的例子中,通過for循環遍歷列表來批量生成工廠函數,但是只有列表的最后一個元素起到了默認值作用,前面的元素均沒有生效,應改為下述寫法:
def recoder(strname, age):n print('name:', strname, 'age:', age)nnndef makerecoders():n acts = []n for i in ["cui", "ma"]:n acts.append(lambda age, i=i: recoder(i, age)) # 將循環值i作為參數傳入匿名函數n return actsnnnfor a in (makerecoders()):n a(age=32) # name: cui age: 32 n name: ma age: 32







