Python中的functools模块

  1. update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)
  • 功能类似上面代码中copy_property功能,帮助修改包装函数属性
  • wrapper 包装还是、被更新者(需要更新者)
  • wrapped 被包装函数、数据源
  • assigned=WRAPPER_ASSIGNMENTS 其中WRAPPER_ASSIGNMENTS是一个元组
  • WRAPPER_ASSIGNMENTS = (’__module__’, ‘__name__’, ‘__qualname__’, ‘__doc__’,
    ‘__annotations__’)
  • updated=WRAPPER_UPDATES 其中 WRAPPER_UPDATES是一个元组
  • WRAPPER_UPDATES = (’__dict__’,)
  • 注意:在update_wrapper函数调用后,会新增加一个__wrapped__属性,记录了被包装函数wrapped
  • 原码如下:
  • Python中的functools模块_sed

  • 使用update_wrapper函数修改属性值如下:
import datetime,time,functools

#定义修改注解函数,并将修改注解函数柯里化
def logg(fn):
def copy_property(tofn):
tofn.__name__ = fn.__name__
tofn.__doc__ = fn.__doc__
return tofn #注意,这里需要将原函数返回。不然元函数值为none
return copy_property

def strengthen(fn):
"""我是strengthen方法的注解"""
# @logg(fn) #等效于 wrapper = logg(fn)(wrapper)
def wrapper(*args,**kwargs):
"""我是wraper方法的注解"""
starttime = datetime.datetime.now()
fn(*args,**kwargs) #这里使用原理的加法器做运算
elapsedtime = datetime.datetime.now() - starttime
print("耗时:{}".format(elapsedtime.total_seconds()))
functools.update_wrapper(wrapper,fn) #效果类似于logg(fn)(wrapper)
return wrapper

@strengthen #等效于add = strengthen(add)
def add(x,y):
"""我是add方法的注解"""
print("开始计算{}+{}".format(x,y))
time.sleep(1)
hh = x + y
print("结果为:{}".format(hh))
return hh

add(5,4)
print(add.__name__)
print()
add.__wrapped__(5,6) #__wrapped属性值中保存了原来的被包装函数
# datetime.datetime.now()
  • 运行结果如下:
  • 其代码还可以修改为如下方式:
import datetime,time,functools

#定义修改注解函数,并将修改注解函数柯里化
def logg(fn):
def copy_property(tofn):
tofn.__name__ = fn.__name__
tofn.__doc__ = fn.__doc__
return tofn #注意,这里需要将原函数返回。不然元函数值为none
return copy_property

def strengthen(fn):
"""我是strengthen方法的注解"""
# @logg(fn) #等效于 wrapper = logg(fn)(wrapper)
@functools.wraps(fn) #与logg(fn)类似
def wrapper(*args,**kwargs):
"""我是wraper方法的注解"""
starttime = datetime.datetime.now()
fn(*args,**kwargs) #这里使用原理的加法器做运算
elapsedtime = datetime.datetime.now() - starttime
print("耗时:{}".format(elapsedtime.total_seconds()))
# functools.update_wrapper(wrapper,fn) #效果类似于logg(fn)(wrapper)
return wrapper

@strengthen #等效于add = strengthen(add)
def add(x,y):
"""我是add方法的注解"""
print("开始计算{}+{}".format(x,y))
time.sleep(1)
hh = x + y
print("结果为:{}".format(hh))
return hh

add(5,4)
print(add.__name__)
print()
add.__wrapped__(5,6) #__wrapped属性值中保存了原来的被包装函数
# datetime.datetime.now()
  • 运行结果如下:
  • Python中的functools模块_sed_02

上面改造中使用了@functools.wraps(fn)

  1. wraps(wrapped,assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES)
  • 类似logg功能
  • wrapped被包装函数
  • assigned=WRAPPER_ASSIGNMENTS 其中WRAPPER_ASSIGNMENTS是一个元组
  • WRAPPER_ASSIGNMENTS = (’__module__’, ‘__name__’, ‘__qualname__’, ‘__doc__’,
    ‘__annotations__’)
  • updated=WRAPPER_UPDATES 其中 WRAPPER_UPDATES是一个元组
  • WRAPPER_UPDATES = (’__dict__’,)
  • 注意:会新增加一个__wrapped__属性,记录了被包装函数wrapped
  • wraps函数原码:
  • Python中的functools模块_functools模块_03

  1. reduce(function,iterable[,initializer]) #归纳计算。 通过指定函数将集合中的数据按照指定方式规整为一个数据。
  • function 是个函数,用来定义规整方式
  • iterables 可迭代对象
  • initializer 设置规整时的初始值,默认值为None,会获取iterables中的第一个元素作为初始值。
  • Python官方伪代码如下:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return
  1. partial(func, *args, **keywords)->fun #偏函数。
  • 偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回
  • 从partial生成的新函数,是对函数的封装
  • func #需要封装的函数
  • *args #需要固定的位置参数
  • **kwywords #需要固定的关键字参数
  • 简单原码如下:
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return
  • 示例:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

lru_cache缓存装饰器

  • functools.lru_cache(maxsize=128,typed=False) #cache缓存。为函数添加缓存机制。通过一个字典缓存被装饰函数的调用和返回值。
  1. Least-recently-used装饰器。lru,最近最少使用。cache缓存
  2. maxsize:如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂时,LRU功能执行得最好
  3. 如果typed设置为True,则不同类型的函数参数将单独缓存。例如f(3)和f(3.0)将被视为具有不同结果的不同调用
  4. 示例
import functools
import time

@functools.lru_cache()
def add(x,y):
time.sleep(1)
return x + y

print(add(4,5))
print(add(4.0,5))
print(add(4,6))
print("--------------")
print(add(4,5)) #会使用缓存,无需等待,直接出结果
print(add(4.0,5)) #会使用缓存,无序等待,直接出结果
print(add(4,6)) #会使用缓存,无序等待,直接出结果
print("-------------------")
print(add(4,y=6)) #无法使用缓存
print(add(x=4,y=6)) #无法使用缓存
print(add(y=6,x=4)) #无法使用缓存
  • lru_cache装饰器
  1. 斐波拉契数列递归方法的改造
import functools,datetime

def fib(n):
return 1 if n<3 else fib(n-1)+fib(n-2)

@functools.lru_cache(maxsize=None)
def fib2(n):
return 1 if n<3 else fib2(n-1)+fib2(n-2)

dt = datetime.datetime.now()
print([fib2(i+1) for i in range(35)])
dt1 = datetime.datetime.now()
print("(fib2)使用lru_cache缓存技术,用时:{}".format((dt1-dt).total_seconds()))
print([fib(i+1) for i in range(35)])
dt2 = datetime.datetime.now()
print("(fib)不使用lru_cache缓存技术,用时:{}".format((dt2-dt1).total_seconds()))
  • lru_cache装饰器应用
  1. 使用前提
  • 同样的函数参数一定得到同样的结果
  • 函数执行时间很长,且要多次执行
  1. 本质是函数调用函数=》返回值
  2. 缺点
  • 不支持缓存过期,key无法过期、失效
  • 不支持清除操作
  • 不支持分布式,是一个单机的缓存
  1. 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询
  • 实现一个cache装饰器,实现可过期被清除的功能
  1. 简化设计,函数的形参定义不包含可变位置参数、可变关键字参数和keyword-only参数 可以不考虑缓存大小,也不用考虑缓存满了之后的换出问题
import inspect,functools,datetime

def xdd_time(fn):
"""
时间统计,统计函数执行时间
:param fn: 需要装饰的函数
:return: 包装后的fn
"""
@functools.wraps(fn)
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
req = fn(*args,**kwargs)
timeout = datetime.datetime.now() - start
print("{}方法耗时:{},args={},kwargs={},\n执行结果:req={}".format(fn.__name__,timeout.total_seconds(),args,kwargs,req))
return req
return wrapper

def xdd_cache(duration = 5):
def _xdd_cache(fn):
"""
为函数生成缓存机制,具有有效时长功能
:param fn:
:param duration:缓存时长,单位秒,如果为None表示全部缓存,不计算时长
:return:
"""
xdd_dict = {} #定义一个字典,用来存参数与对应的计算结果
sig = inspect.signature(fn) #对函数进行签名

@functools.wraps(fn)
def wrapper(*args,**kwargs):
paramestr = getparamestr(args,kwargs) #根据参数获取字符串
req = xdd_dict.get(paramestr,None)
if req is None:
req = fn(*args,**kwargs),datetime.datetime.now()
xdd_dict[paramestr] = req
elif not duration is None and (datetime.datetime.now() - req[1]).total_seconds() > duration: #超时
print("缓存超时")
req = fn(*args, **kwargs),datetime.datetime.now()
xdd_dict[paramestr] = req
return req[0]

def getparamestr(args,kwargs)->str:
"""
格式化参数,将参数格式化为指定格式的字符串
:param args:
:param kwargs:
:return:
"""
parames = sig.parameters
pstr = ()
isargs = True #标记是否还有位置参数
kwdict = {}
for k,(name,v) in enumerate(parames.items()):
v:inspect.Parameter = v
if name == "args": #位置参数已经全部匹配完成
isargs = False
pstr += ("args",tuple(args[k+1:])) #记录剩下的位置参数
continue
if name == "kwargs": #关键字位置参数已经匹配完
pstr += ("kwargs",tuple((k,kwargs[k]) for k in kwargs.keys()-kwdict.keys()))
break
if isargs: # 位置参数还没匹配完
if k<len(args):
pstr += (name, args[k])
else: #实际传参中位置参数已经传完
kwval = kwargs.get(name,v.default)
kwdict[name] = kwval # 记录已经出现的位置参数
pstr += (name,kwval)
else:
kwval = kwargs.get(name, v.default)
kwdict[name] = kwval #记录已经出现的位置参数
pstr += (name,kwval)
# print(k,name,v.name,v.annotation,v.default)
return pstr
return wrapper
return _xdd_cache

# @gdy_cache
# def add(x,y:int=5,*args,z=4,b:int,c=6,**kwargs):
# import time
# time.sleep(3)
# return x+y
@xdd_time
@xdd_cache()
def add(x=4,y=5):
import time
time.sleep(3)
return x+y
print("开始计算")
print(1,add(4,5))
import time
time.sleep(5)
print(2,add(4))
# print(3,add(y=5))
# print(4,add(x=4,y=5))
# print(5,add(y=5,x=4))
# print(6,add())

Python中的functools模块_缓存_04