工厂函数
普通工厂函数的实现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