Python学习之 ---functools+reduce方法+ partial偏函数+lru_cache缓存复用

之前在将装饰器时使用到了functool里的wpapsfunctool 里还有几个很重要的部分值得深入学习 .

  • 包括:
    • reduce方法
    • partial偏函数
    • lrc _cache_

接下来将对这些方法进行详细的叙说:

  • reduce方法
    • reduce方法,顾名思义就是减少
    • reduce(function, sequence[, initial]) -> value ★★★★
    • 可迭代对象不能为空;初始值没提供就在可迭代对象中取一个元素
from functools import reduce 
print(reduce(lambda x,y:x+y, range(5)))#先传入0,和1,相加得到的1 ,此时x赋值为1,后传入2,与x=1 相加,而后在次赋值给x,在传入一个数3,依次往后直到结束.
print(sum(range(5),10))  # sum函数的带初始值相加
print(reduce(lambda x,y:x*y,range(1,5),10)) # reduce 函数带初始值的相乘
>>> 
10
20
240
# reduce  的函数定义
# 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 value

  • partial方法

  • 偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一 个新的函数并返回

  • 从partial生成的新函数,是对原函数的封装 ★★★★

  • partial方法举例一

    import functools
    def add(x, y) -> int: 
        return x + y
    newadd = functools.partial(add, y=5)  #偏函数返回的是新函数  
    print(newadd(7))
    print(newadd(7, y=6))
    print(newadd(y=10, x=6))
    import inspect
    print(inspect.signature(newadd))
    >>>
    12
    13
    16
    (x, *, y=5) -> int   
    

partial方法举例二:

import functools
def add(x, y, *args) -> int: 
    print(args) # 返回的是元组
    return x + y
newadd = functools.partial(add, 1,3,6,5)
print(newadd(7))  # 1 ,3 分别给x, y ,剩下的给args 
print(newadd(7, 10))
# print(newadd(9, 10, y=20, x=26)) # print(newadd())  函数参数定义重复,会报错
import inspect
print(inspect.signature(newadd))
>>>
(6, 5, 7)
4
(6, 5, 7, 10)
4
(*args) -> int 
#注意 !!!
def add(x:int, y:int, *args:int) -> int: 
  pass
newadd = functools.partial(add, 1)
print(inspect.signature(newadd))# 这里又会返回什么呢??
>>>(y:int, *args:int) -> int  # 会返回出固定的值之外其他参数的声明
  • partial函数本质 ----查看函数源码

    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 newfunc
    #python  定义的源码
    #############################
    def add(x,y): 
        return x+y
    # foo = partial(add,4)
    # foo(5)
    g =partial(add,4)(5)
    print(g)
    
    
    
    
    

    分析functools.wraps的实现

    def update_wrapper(wrapper,
                       wrapped,
                       assigned = WRAPPER_ASSIGNMENTS,# 大写当成常量
                       updated = WRAPPER_UPDATES):
        for attr in assigned:
            try:
                value = getattr(wrapped, attr) #  提取属性值
            except AttributeError:
                pass
            else:
                setattr(wrapper, attr, value)  #更该属性值
        for attr in updated:
            getattr(wrapper, attr).update(getattr(wrapped, attr, {}))# 更新字典
        # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
        # from the wrapped function when updating __dict__
        wrapper.__wrapped__ = wrapped
        # Return the wrapper so this can be used as a decorator via partial()
        return wrapper
    def wraps(wrapped,
              assigned = WRAPPER_ASSIGNMENTS,
              updated = WRAPPER_UPDATES):
        return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)
    # 调用partial偏函数,将update_wrapper(函数名)里的三个参数固定.这样只有一个wrapper 没有固定.返回的还是一个函数  --->wrapper
    
  • @functools.lru_cache方法

      @functools.lru_cache(maxsize=128, typed=False)  
    
  • Least-recently-used装饰器。lru,最近最少使用。cache缓存

  • 如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂 时,LRU功能执行得最好

  • 如果typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不 同结果的不同调用

  • 举例

import functools 
import time
@functools.lru_cache() # 是一个函数,所以在调用时需要加()
def add(x, y, z=3):
    time.sleep(z) 
    return x + y
add(4, 5)
add(4.0, 5)
add(4, 6)
add(4, 6, 3)
add(6, 4) 
add(4, y=6) 
add(x=4,y=6)
# @functools.lru_cache() 会检测输入的参数在最近的内存中是否存在,如果如在则立即返回z

思考:缓存的机制是什么?

  • lru_cache装饰器

    • 通过一个字典缓存被装饰函数的调用和返回值
    • key是什么?分析代码看看

lru_cache装饰器在内存中是如何比较输入的参数与内存中现存的是否一致.

在内存中是生成的一个元组,然后将元组转化成列表,最后在通过魔术方法转化成字典,最后字典可以hash,所以快速判断内存中是否有该值.

源码分析如下:

class _HashedSeq(list): #  魔术方法  用来将下面生成的列表转化为可hash的 字典(列表本不可以hash)
    __slots__ = 'hashvalue'
    def __init__(self, tup, hash=hash):
        self[:] = tup  #转为元组
        self.hashvalue = hash(tup)#hash 
    def __hash__(self):
        return self.hashvalue  #返回hash值
#  创建字典的key 值
def _make_key(args, kwds, typed,  
             kwd_mark = (object(),),
             fasttypes = {int, str, frozenset, type(None)},
             sorted=sorted, tuple=tuple, type=type, len=len):
    key = args # 元组类型
    if kwds:  # 如果存在关键字传参,kwds会是一个字典,如果字典不是空,则与前面的元组相加,
        sorted_items = sorted(kwds.items())# 给字典排序,如果输入的关键字传参是一样的(不论顺序),进行排序后得到的值肯定一致,这样接下来的加入元组顺序也是一致的,后面的hash也是一致,保证了计算时能在缓存中找到一样的值.
        key += kwd_mark  #相当于一个标记,(object(),)  也是一个元组
        for item in sorted_items:
            key += item # 元组加入字典的k,v值
    if typed:
        key += tuple(type(v) for v in args) # 如果type给的是True(默认为False,  3.0  和 3 当成一个类型处理,在上述元组末尾加上该类型的type
        if kwds: 同样判断kwds
            key += tuple(type(v) for k, v in sorted_items)
    elif len(key) == 1 and type(key[0]) in fasttypes:
        return key[0]
    #如果只有一个参数并且其数据类型已知为缓存它的哈希值,则返回该参数时不带包装。这个节省空间并提高查找速度。
    return _HashedSeq(key)s

举例练习:

分析下面的代码,可以得出参数在装饰器中是如何存为元组的.

functools._make_key((4,6),{'z':3},False) # (4,6,object(),'z',3)
functools._make_key((4,6,3),{},False) # (4,6)
functools._make_key(tuple(),{'z':3,'x':4,'y':6},False)# ('z',3,'x',4,'y',6)
functools._make_key((),{'z':3,'x':4,'y':6}, True)# ('z',3,'x',4,'y',6)
  • lru_cache装饰器

    • 斐波那契数列递归方法的改造

之前用循环的方法求解过斐波那契数列,在递归时,需要求解每一个fib()的值,需要消耗很长时间,在这里能否用lrc_cache 进行相应的改进呢?

代码如下:

import functools
@functools.lru_cache() # maxsize=None 
def fib(n):
	return 1 if n<3 else fib(n-1) + fib(n-2) print([fib(i+1) for i in range(35)])
fib(35)
# 使用改进后的代码求解斐波那契数列,其速度快了很多,因为在内存中存取了前面计算的值,直接读取即可.
  • lru_cache装饰器应用

    • 使用前提

      • 同样的函数参数一定得到同样的结果

      • 函数执行时间很长,且要多次执行

  • 本质是函数调用的参数=>返回值

  • 缺点

    • 不支持缓存过期,key无法过期、失效

    • 不支持清除操作

    • 不支持分布式,是一个单机的缓存

  • 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询

猜你喜欢

转载自blog.csdn.net/qq_40498551/article/details/89526922
今日推荐