学习装饰器之路
一、柯里化currying
A、柯里化currying之【柯里化1】:
指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数.
举例: z = f(x, y) 转换成 z = f(x)(y)的形式
举例:将加法函数柯里化def add(x, y):
return x + y
B、柯里化currying之【柯里化2】:
举例:将加法函数柯里化
def add(x, y):
return x + y
转换如下:
def add(x):
def _add(y):
return x+y
return _add
add(5)(6)
通过嵌套函数就可以把函数转换成柯里化函数
二、装饰器
需求1:一个加法函数,想增强它的功能,能够输出被调用过的以及调用的参数信息。def add(x,y):
return x+y
增加信息输出功能:
def add(x,y):
print('call add,x+y') # 日志输出到控制台。
return x+y
上面的加法函数是完成了需求,但是有以下的缺点:
a.打印语句的耦合太高
b.加法函数属于业务功能,而输出信息的功能,属于非业务能功能代码,不应该放在业务加法函数中。
需求2:做好了业务的功能,但是fn函数调用传参是个问题。
代码如下:
def add(x,y): return x+y def logger(fn): print('begin') #增强的输出 x = fn(4,5) print('end') # 增强的功能。 return x print(logger(add))执行结果:
begin end 9需求3: 解决了传参的问题,进一步改变。
代码如下:
def add(x,y): # print(y) return x+y def logger(fn,*args,**kwargs): print('begin') x = fn(*args,**kwargs) print('end') return x print(logger(add,5,y=60))执行结果:
begin #60 end 65A、柯里化的简化:
代码如下:
def add(x,y): return x+y def logger(fn): def wrapper(*args,**kwargs): print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper #------------------------# # add = logger(add) # print(add(x=5,y=10)) #------------------------# #上面二行用下面一行代码替换,这就是柯里化: print(logger(add)(5,y=10))执行结果:
begin end 15B、装饰器语法糖:
代码如下:
def logger(fn): def wrapper(*args,**kwargs): print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper @logger #前面的@logger 等价于add = logger(add) def add(x,y): return x+y print(add(40,45))
#@logger 是什么?答:等价于add = logger(add)这就是装饰器语法。
执行结果:
begin end 85C、装饰器(无参):
装饰器是一种非侵入式的代码
它是一个函数。
函数作业它的形参。
返回值也是一个函数。
可以使用@functionname方式,简化调用。
装饰器和高阶函数
装饰器是高阶函数,但装饰器是对传入函数的功能的装饰。(功能增强)
【装饰器举例1】:
代码如下:
import datetime,time def logger(fn): def wrap(*args,**kwargs): #before 功能增强。 print('args={},kwargs={}'.format(args,kwargs)) start = datetime.datetime.now() ret = fn(*args,**kwargs) #after 功能增强。 duration = datetime.datetime.now() -start print("function {} took{}s.".format(fn.__name__,duration.total_seconds())) return ret return wrap @logger # @logger相当于add = logger(add) def add(x,y): print('=======call add=========') time.sleep(2) return x+y print(add(4,y=7))执行结果:
args=(4,),kwargs={'y': 7} =======call add========= function addtook2.000115s. 11三、文档字符串
A、文档字符串:
python 是文档字符串Documentation Strings
在函数语句块的第一行,且习惯是多行的文本,所以多使用三引呈号。
惯例是首字母大写,第一行写概述,空一行,第三行写详细描述。
可以使用特殊属性__doc__访问这个文档
代码如下:
def add(x,y): """this is a function of addition""" a= x+y return x+y print("name ={}\ndoc={}".format(add.__name__,add.__doc__)) print(help(add))执行结果:
name =add doc=this is a function of addition Help on function add in module __main__: add(x, y) this is a function of addition NoneB、文档字符串副作用:
代码如下:
def logger(fn): def wrapper(*args,**kwargs): 'I am wrapper' print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper @logger # @logger 相当于:add = logger(add) def add(x,y): """this is a function for add""" return x+y print("name={},doc={}".format(add.__name__,add.__doc__))执行结果:
name=wrapper,doc=I am wrapper
以上代码中原函数属性都被替换了,但打印出来的结果已不是原来的属性了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决。
# 提供了一个函数,被封装函数属性===copy===》包装函数属性。
代码如下:
def copy_properties(src,dst): # 可以改造成装饰器。 dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ def logger(fn): def wrapper(*args,**kwargs): 'I am wrapper' print('begin') x = fn(*args,**kwargs) print('end') return x copy_properties(fn,wrapper) return wrapper @logger # add = logger(add) def add(x,y): """ this is function for add""" return x+y print("name={},doc={}".format(add.__name__,add.__doc__))执行结果:
name=add,doc= this is function for add #实现查看对应函数的属性。
五、过渡:【把装饰器的copy_properties属性改成带参装饰器】:通过copy_properties 函数将被包装函数的属性覆盖掉包装函数。
凡是被装饰的函数都有需要复制这些属性,这个函数很通用。
可以将复制的函数构造成装饰器函数,带参装饰器。
A、带参装饰器:提供一个函数,被封装函数属性===copy===》包装函数属性,改造成带参的装饰器。
代码如下:
def copy_properties(src): def _copy(dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ return dst return _copy def logger(fn): @copy_properties(fn) #wrapper = wrapper(fn)(wrapper) def wrapper(*args,**kwargs): "I am wrapper" print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper @logger #add = logger(add) def add(x,y): """this is a function for add""" return x+y print("name={},doc={}".format(add.__name__,add.__doc__))
执行结果
name=add,doc=this is a function for add六、带参装饰器:
需求: 获取函数的执行时长,对时长超过阈值的函数记录一下。
import datetime,time def logger(duration): def _logger(fn): @copy_properties(fn) # wrapper = wrapper(fn)(wrapper) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() print('so slow') if delta > duration else print('so fast') return ret return wrapper return _logger @logger(5) #add = logger(5)(add) def add(x,y): time.sleep(3) return x+y print(add(5,6))执行结果:
so fast 11七、带参装饰器:
它是一个函数
函数作为它的形参
返回值是一个不带参的装饰器函数
使用@functionname (参数列表)方式调用
可以看做在装饰器外层又加了一层函数
A、带参装饰器1:
将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
代码如下:
import datetime,time,functools def copy_properties(sti): def _copy(pte): pte.__name__ = sti.__name__ pte.__doc__ = sti.__doc__ return pte #一定要return返回值,要不会返回None值,要不然下面函数调用的结果都是None值,没有意义了。 return _copy #一定要return返回值,要不会返回None值,要不然下面函数调用的结果都是None值,没有意义了。 def logger(duration,func=lambda name,duration:print('{}took {}s'.format(name,duration))): def _logger(fn): @copy_properties(fn) #相当于:wrapper =wrapper copy_properties(fn)(wrapper) #在这里由下面这样演变过来的: @_copy wrapper_copy(wrapper) def wrapper(*args,**kwargs): start =datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() if delta > duration: func(fn.__name__,duration) return ret return wrapper return _logger @logger(2) #add= logger(add) def add(x,y): 'i am add' return x+y print(add(4,5),add.__name__,add.__name__,add.__doc__) print(logger(add))执行结果:
9 add add i am add <function logger.<locals>._logger at 0x00000000023748C8> Process finished with exit code 0八、functools模块:
A、functools 模块1
functools.update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)
wrapper包装函数,被更新者,wrapped被包装函数、数据源。
元组 WRAPPER_ASSIGNMENTS中是要被覆盖的属性。
'_module_','_name_','_qualname_','_doc_','_annotations_'
元组WRAPPER_UPDATES中是要被更新的属性,_dict_属性字典。
增加一个_wrapped_属性,保留着wrapped属性
functools模块1举例:functools.update_wrapper(wrapper,fn)#包装函数wrappe和被包装函数fn
代码如下:
import datetime,time,functools def logger(duration,func=lambda name,duration:print('{} took {}s.'.format(name,duration))): def _logger(fn): def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() if delta > duration: func(fn.__name__,duration) return ret return functools.update_wrapper(wrapper,fn)#包装函数wrappe和被包装函数fn return _logger @logger(5) #add = logger(5)(add) def add(x,y): time.sleep(1) return x+y print(add(5,6),add.__name__,add.__wrapped__,add.__dict__,sep='\n')执行结果:
11 add <function add at 0x0000000004E4A9D8> {'__wrapped__': <function add at 0x0000000004E4A9D8>B、functools 模块2 :
@functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)
类似copy_properties功能
wrapped 被包装函数
元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
'_module_','_name_','_qualname_','_doc_','_annotations_'
模块名,名称,限定名,文档,参数注解。
元组WARAPPER_UPDATES中是要被更新的属性,_dict_属性字典
增加一个_wrapped_属性,保留着wrapped函数
functools模块2举例1:@functools.wraps(fn)
代码如下:
import datetime,time,functools def logger(duration,func=lambda name,duration:print('{} took {}s'.format(name,duration))): def _logger(fn): @functools.wraps(fn) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() -start).total_seconds() if delta > duration: func(fn.__name__,duration) return ret return wrapper return _logger @logger(5) #相当于add = logger(5)(add) def add(x,y): time.sleep(1) return x+y print(add(5,6),add.__name__,add.__wrapped__,add.__dict__,sep= '\n')执行结果:
11 add <function add at 0x0000000004E4AAE8> {'__wrapped__': <function add at 0x0000000004E4AAE8>}C、functools模块2举例2::@functools.wraps(fn)
functools模块2举例2:
代码如下:
import functools,datetime,time # def copy_perporties(tif): # def _copy(fit): # fit.__name__ = tif.__name__ # fit.__doc__ = tif.__doc__ # return fit # return _copy def logger(duration=5,func=lambda name,duration:print(name,duration)): def _logger(fn): #@copy_properties(fn) #前面6行和这一行是柯里化装饰器调用同下面的实现方法基本一样。 @functools.wraps(fn) #后面跟被包装函数fn def wrapper(*args,**kwargs): 'i am wrapper' print('args={} kwargs={}'.format(args,kwargs)) start = datetime.datetime.now() ret = fn(*args,**kwargs) print(id(fn),'1___________') delta = (datetime.datetime.now()-start).total_seconds() if delta > duration: func(fn.__name__,duration) print("funcation {} took {} s".format(fn.__name__,duration)) return ret #copy_perproties(fn,wrapper) #functools.update_wrapper(wrapper,fn) return wrapper return _logger @logger() #相当于:add= loger(add) #logger(5)-->_logger add = _logger(add) def add(x,y): 'this is a add funcation' print('====call add======') time.sleep(2) return x+y print(add(4,y=7),add.__name__,add.__doc__,add.__wrapped__,sep=’\n’) print(id(add.__wrapped__),'2____________')执行结果:
args=(4,) kwargs={'y': 7} ====call add====== 42548352 1___________ funcation add took 5 s 11 add this is a add funcation <function add at 0x0000000002893C80> 42548352 2____________ Process finished with exit code 0