柯里化与装饰器
柯里化与装饰器在Python中应用广泛,通过使用柯里化可以给实现装饰器提供可能.
那么什么叫做装饰器么?先做个简单的引导
装饰器本来是一门技术,但是在python中发扬光大了。
举例:
如果有一个情况,要定义一个函数,功能是做排序,三下五除二写出来了,但是客户过几天又说,哎你能不能给这个函数加一个时间运行统计,我想知道这个函数内部占用时间较大的运算。如果再去改代码的话,相当于把代码死死写在函数里,这叫做硬编码,不具有适用性,如果客户要在提别的要求,还要修改,很不方便.
但是如果运用装饰器,就能做到了业务功能和附加功能的分离,并且解决了业务函数的参数调用的问题。
首先我们来学习一下什么叫做柯里化:
柯里化
指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数
z = f(x, y) 转换成 z = f(x)(y)的形式
def add(x, y):
return x + y
原来函数调用为add(4, 5) ,柯里化目标是add(4)(5) 。如何实现?
每一次括号说明是函数调用,说明add(4)(5) 是2次函数调用。
add(4)(5)
等价于
t = add(4)
t(5)
也就是说add(4)应该返回函数。
def add(x):
def _add(y):
return x + y
return _add
add(100, 200)
fn=add(4)
fn(5)
通过嵌套函数就可以把函数转成柯里化函数。
举例:
给定三个数,x,y,z,求三个数的和.函数调用
add(4)(5,6) add(4)(5)(6).add(4,5)(6)
# 题一
def add(x):
def _add(y,z):
return x+y+z
return _add
fn =add(4)
fn(5,6)
#题二
def add(x):
def _add(y):
def __add(z):
return x+y+z
return __add
return _add
print(add(4)(5)(6))
fn=add(4)
fn(5)(6)
# 题三
def add(x,y):
def _add(z):
return x+y+z
return _add
fn =add(4,5)
fn(6)
了解了柯里化,那么装饰器的学习算是入门了,接下来看看柯里化在装饰器里是如何大显神通的.
装饰器
需求
● 一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
def add(x, y):
return x + y
增加信息输出功能
def add(x, y):
print("call add, x + y") # 日志输出到控制台
return x + y
●上面的加法函数是完成了需求,但是有以下的缺点
□ 打印是一个功能,这条语句和add函数耦合太高
□ 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数add中
●下面代码做到了业务功能分离,但是fn函数调用传参是个问题
def add(x,y):
return x+y
def logger(fn):
print('before')
print('add function :{} {}'.format(4, 5))
ret = fn(4,5)
print('after')
return ret
logger(add)
解决传参的问题,进一步改变
def add(x,y):
return x+y
def logger(fn,*args,**keyward):
print('before')
print('add function :{}{}'.format(args,keyward))
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
print('after')
return ret
logger(add,4,5)
logger(add,x=4,y=5)
进行柯里化
def add(x,y):
return x+y
def logger(fn):
def inner(*args,**keyward):
print('before')
print('add function :{}{}'.format(args,keyward))
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
print('after')
return ret
return inner
logger(add)(4,5)
#### t= logger(add)# 首先将logger函数传给函数t ,然后add函数作为参数传给了logger ,右logger函数被柯里化,所以继续将4,5传入
#### t(4,5)
再一次变形
def add(x,y): # add=f
return x+y
def logger(fn):
def inner(*args,**keyward):
print('before')
print('add function :{}{}'.format(args,keyward))
ret = fn(*args,**keyward) # f(4,5) 这里add已经不是原来的add,但最原始的add函数的引用地址被这里的fn记录,所以没有清零,故仍会调用原始的add函数
print('after')
return ret
return inner
add=logger(add)
add(4,5)
装饰器语法糖
def logger(fn):
def inner(*args,**keyward):
print('before')
print('add function :{}{}'.format(args,keyward))
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
print('after')
return ret
return inner
@logger # add=logger(add) 将装饰器下面的函数标识符作为一个实参传入,函数调用结束的返回值会重新覆盖下面标识符值,这里只支持一个参数的传参.
#装饰器不给原函数增添一条语句还能满足其要求,需要时打开即可
def add(x,y):
return x+y
add(4,5) ## inner(4,5) # logger(add)(40,50)
装饰器(无参)
● 它是一个函数
●函数作为它的形参。无参装饰器实际上就是一个单形参函数
●返回值也是一个函数
●可以使用@function-name方式,简化调用
注:此处装饰器的定义只是就目前所学的总结,并不准确,只是方便理解
装饰器和高阶函数
●装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
举例:
计算函数运行时间
import datetime
import time
def logger(fn):
def wrapper(*args,**keyward):
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
print("{} took {:.2f}s".format(fn.__name__,delta))
return ret
return wrapper
@logger
def add(x,y):
time.sleep(2)
return x+y
add(4,5)
如何理解装饰器:
可以将装饰器假设成装裱的一幅画,其叠加顺序与各层含义如图所示:
文档字符串
●Python的文档
●Python文档字符串Documentation Strings
●在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
●惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
●可以使用特殊属性__doc__访问这个文档
def add(x,y):
"""This is a function of addition"""
return x + y
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
这里输出的结果是原add函数的名字么?如果不是,那又不想让用户知道函数被什么函数封装,该怎么做么?
先来复习下namedtuple:
from collections import namedtuple
Point= namedtuple('Point',['x','y'])
type(Point)
>> type #Point 是一个类型
p1=Point(4,5)
print(p1)
>>Point(x=4, y=5)
Point(x=20,y=10)
p1.x p2.y
>>(20,10)
Student = namedtuple('Stu','name,age')
Student
>>'__main__.stu' #为标识符,名字为Stu ,名字给人看的,程序员使用标识符
tom = Student('tom',20)
tom
>>Stu(name='tom',age=20)
jerry = Student('Jerry',18)
jerry
>> Stu(name='Jerry',agr = 18)
#其他:
x =4
y=10
s1=f"{x}-->{y}"
s1
>>'4-->10'
s2=f'{P1}’ #
>>'Point(x=4,y=5)'
函数的标识时是函数调用时用的,函数名称是给用户看的.
那么现在回到上面定义的装饰器函数中,该如何更改原函数的名称呢?
import datetime
import time
def logger(fn):
def wrapper(*args,**keyward):
"my name is wrapper"
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
print("{} took {:.2f}s".format(fn.__name__,delta))
return ret
wrapper.__name__=fn.__name__# 修改标识符名
wrapper.__doc__=fn.__doc__ # 修改文档字符串名
# 这里可否修改为函数呢?
#def copy_properties(src,dest)# dest 目的操作数
# dest.__name__=src.__name__
# dest.__doc__=src.__doc__
#copy_properties(fn,wrapper)
#定义好后,将函数提取到外面
return wrapper
@logger
def add(x,y):
"this is a add function"
time.sleep(2)
return x+y
# add(4,5) ## inner(4,5) # logger(add)(40,50)
print(add.__name__,(add.__doc__)
将上面的函数进一步优化,将修改name 和文档字符串提取到函数外部,并进行柯里化
import datetime
import time
# def coy_properties(src,dest):
# dest.__name__=src.__name__
# dest.__doc__=src.__name__
#进行柯里化
def copy_properties(src):
def _properties(dest):
dest.__name__=src.__name__
dest.__doc__=src.__doc__
# return dest
return _properties
def logger(fn):
def wrapper(*args,**keyward):
"my name is wrapper"
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
print("{} took {:.2f}s".format(fn.__name__,delta))
return ret
copy_properties(fn)(wrapper)
return wrapper
@logger
def add(x,y):
"this is a add function"
time.sleep(2)
return x+y
print(add.__name__,add.__doc__)
带参装饰器
将copy_properties函数在次使用装饰器
import datetime
import time
def copy_properties(src):
def _properties(dest):
dest.__name__=src.__name__
dest.__doc__ =src.__doc__
return dest
return _properties
def logger(fn):
@copy_properties(fn) #wrapper = copy_properties(fn) (wrapper) ==> wrapper =_properties(wrapper)
# 这就是带参装饰器
def wrapper(*args,**keyward):
"my name is wrapper"
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
print("{} took {:.2f}s".format(fn.__name__,delta))
return ret
# copy_properties(fn,wrapper)
# copy_properties(fn)(wrapper) #相当于做这样的操作
return wrapper
@logger
def add(x,y):
"""this is a add function """
time.sleep(2)
return x+y
print(add.__name__,add.__doc__)
functool模块
事实上,python 在内部已经为我们创建好了相同的操作我们只需要调用即可.如下:
import datetime
import time
from functools import update_wrapper ,wraps
def logger(fn):
@wraps(fn)
def wrapper(*args,**keyward):
"my name is wrapper"
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
print("{} took {:.2f}s".format(fn.__name__,delta))
return ret
# update_wrapper(wrapper,fn)#等价于上面的@wraps
return wrapper
@logger
def add(x,y):
"""this is a add function """
time.sleep(2)
return x+y
print(add.__name__,add.__doc__)
还是上面的例子,需要给内部delta 的判断函数进行传参.外部可以修改该怎么办呢??
这里就可以利用外部传参.
举例:
import datetime
import time
from functools import update_wrapper ,wraps
def logger(duration=5):# 在定义一层函数,进行柯里化.
def _logger(fn):
@wraps(fn)
def wrapper(*args,**keyward):
"my name is wrapper"
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
if delta>duration:# 这里的5能都由外部传入??
print("{} took {:.2f}s".format(fn.__name__,delta))
else:
print ('that is ok')
return ret
# update_wrapper(wrapper,fn)#等价于上面的@wraps
return wrapper
return _logger
@logger()#这样就可以单参数传参,括号内可以写数字,也可以不给定值,但括号不能少.
# add=logger()(add)==>add =wrapper
#@logger(duration=6) 如果使用这样的传参,那么上面的柯里化将不在需要.
def add(x,y):
"""this is a add function """
time.sleep(2)
return x+y
add(40,50)
print(add.__name__,add.__doc__)
如果需要把内部定义的判读语句灵活运用,则可以在次把内部定义为一个函数,提取到外部,在传入参数即可.
import datetime
import time
from functools import update_wrapper ,wraps
def x (d,f,du): # 把判断函数丢到外部,方便修改
if d>du:
print("{} took {:.2f}s".format(f.__name__,d))
# else:
# print ('that is ok')
def logger(duration=5,func=x): # 这里不使用柯里化是没有问题的,原因见下附录 #
#将上行函数转化成lambda函数
def logger (duration= 5,func=lambda d,f,du: if d>du )# 该如何修改???
def _logger(fn):
@wraps(fn)
def wrapper(*args,**keyward):
"my name is wrapper"
print('begin to work')
start = datetime.datetime.now()
ret = fn(*args,**keyward)# argas 为一个元组,所以这里需要结构
delta = (datetime.datetime.now()-start).total_seconds()
func(delta,fn,duration)# 调用函数
return ret
return wrapper
return _logger
@logger()
def add(x,y):
"""this is a add function """
time.sleep(2)
return x+y
add(40,50)
print(add.__name__,add.__doc__)
附录:
def add(x,y):
def _add(z):
return x+y+z
return _add
add(4,5)(6)# 只有保证外层为一参既可以满足装饰器调用
装饰器练习:
import datetime, functools
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 > 3: print('too slow')
return ret
return wrapper
@logger
def add(x, y): pass
# add = logger(add)
@logger
def sub(x, y): pass
#sub = logger(sub)
print(add.__name__, sub.__name__)
上面的程序
logger什么时候执行? #装饰器在创建时,函数一创建变会执行
logger执行过几次? # 2次
wraps装饰器执行过几次? # 2次
wrapper的__name__等属性被覆盖过几次? #2次
add.__name__ 打印什么名称? #add 每次函数调用,创建的内容是不一样的.
sub.__name__ 打印什么名称? #sub