Python-functools (reduce,偏函数partial,lru_cache)

枫铃3年前 (2021-07-10)Python278

1、functools模块—reduce()

reduce方法:

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

reduce()实现代码

举例1:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
from functools import reduce
# reduce()
print(reduce(lambda a,x:a + x , range(4))) # 6
print(reduce(lambda a,x:a + x , range(4), 10)) # 16

num_l=[1,2,3,100]
print(reduce(lambda x,y:x+y,num_l,1)) #可以定义起始值,否则默认起始值为第一个元素
print(reduce(lambda x,y:x+y,num_l))
print(reduce(lambda a,x:a + x , []))  # 报错,根据定义看,如果没有初始值,那会去迭代对象取第一个值,但是为空,无法next(it)

2、functools ----partial 方法

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

从 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

举例 1:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)
    ''' 这是什么意思?'''
    # newfunc.func = func
    # print(newfunc.func) # <function add at 0x00000000029DCF28>
    # newfunc.args = args
    # print(newfunc.args) # ()
    # newfunc.keywords = keywords
    # print(newfunc.keywords) # {'y': 5}
    return newfunc

def add(x, y):
    return x + y
newadd = partial(add,y=5)
# print(newadd(4))

举例2:

from functools import partial
import inspect

def add(x:int, y):
    return x + y

newadd = partial(add, 4)
print(inspect.signature(newadd))
print(newadd(5))

# (y)
# 9

newadd = partial(add, y=5)
print(inspect.signature(newadd))
print(newadd(4))

# (x, *, y=5) # 因为 y=5 是关键字传参,还是可以被更新,所以还是会出现的
# 9

newadd = partial(add, x=4,y=5)
print(inspect.signature(newadd))
print(newadd())

# (*, x=4, y=5)
# 9

newadd = partial(add,x=4,y=5)
print(inspect.signature(newadd))
print(newadd(x=10,y=12))

# (*, x=4, y=5)
# 22


# newadd = partial(add, x=5) # 报错,虽然x=5被**kwargs收了,但是 4 传入后是frags,最终赋给x,而后面有x=5,重复
# print(newadd(4))

举例3:针对 from functools import wraps 的分析

'''偏函数作用:把一些函数从多参---变成单参----这些函数就可以当无参装饰器'''
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def update_wrapper(wrapper,                             
                   wrapped,                                                         
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

def wraps(wrapped,                                            @wraps(fn) 等价 w=wraps(fn)(w) 而wraps(fn)就是一个新函数      
          assigned = WRAPPER_ASSIGNMENTS,                       wraps函数中可以看到,传入 w,其他的都是固定值,同时返回一个偏函数
          updated = WRAPPER_UPDATES):                           偏函数只有第一个参数没有定,假设生成一个新函数 new()
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)          这个new() 只需传入 update_wrapper参数即可,当传入后,update_wrapper
                                                                只剩wrapped这个参数, w=wraps(fn)(w)这个第二个参数就是w

from functools import update_wrapper,wraps

def logger(fn):
    @wraps(fn) # w=wraps(fn)(w)结构就是一个柯里化的样式
    def w(*args, **kwargs):
        ''' this is wrapper function'''
        print('执行业务功能之前')
        ret = fn(*args, **kwargs)
        print('执行业务功能之后')
        return ret
    # update_wrapper(w, fn)
    return w

@logger # add = logger(add)  ----> add = wrapper
def add(x, y):
    ''' this is add function'''    # 这个只能放在第一行才能打印!!!!!
    return  x + y

3、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)将被视为具有不同结果的不同调用。

举例 1:

from functools import lru_cache
import time

@lru_cache() # 带参的装饰器
def add(x=[], y=1):
    x.append(y)
    return x

print(add())
print(add()) # 第二次执行,是直接找上次的结果,并不会在追加,如果不加缓存,就会继续追加

print(add([0])) # 不可hash,以为@lru_cache 原码影响。

lru_cache 源码分析:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class _HashedSeq(list):
    """ This class guarantees that hash() will be called no more than>= 'hashvalue'

    def __init__(self, tup, hash=hash):
        self[:] = tup
        self.hashvalue = hash(tup)

    def __hash__(self):
        return self.hashvalue

def _make_key(args, kwds, typed,
             kwd_mark = (object(),),
             fasttypes = {int, str, frozenset, type(None)},
             tuple=tuple, type=type, len=len):
    """Make a cache key from optionally typed positional and keyword arguments

    The key is constructed in a way that is flat as possible rather than
    as a nested structure that would take more memory.

    If there is># All of code below relies># Formerly, we sorted() the kwds before looping.  The new way is *much*
    # faster; however, it means that f(x=1, y=2) will now be treated as a
    # distinct call from f(y=2, x=1) which will be cached separately.
    key = args
    if kwds:
        key += kwd_mark
        for item in kwds.items():
            key += item
    if typed:
        key += tuple(type(v) for v in args)
        if kwds:
            key += tuple(type(v) for v in kwds.values())
    elif len(key) == 1 and type(key[0]) in fasttypes:
        return key[0]
    return _HashedSeq(key)

def lru_cache(maxsize=128, typed=False):

# 从原码可以看出,args 只能是元组,因为最终返回  _HashedSeq(key),而事实上,class _HashedSeq(list): 是元组继承而来,def __init__(self, tup, hash=hash):
# 而且必须可hash,所以list,set,dict都不能,
# 对传入的参数,位置参数赋值给key,之后会添加一个 object() 形成一个新的 tuple,再将关键字参数,形成的字典,以元组形式与之前的
# 的元组相加,形成新的元组,最后加一个type 即元素的类型。
# 如下:
# from functools import  _make_key
# key = _make_key((1,2,3),{'a':1},False)
# print(key) # [1, 2, 3, <object object at 0x000000000019E160>, 'a', 1]
#
# print(hash(key)) # -147545184494388729

举例 2:

from functools import lru_cache
import time

@lru_cache()
def add(x=4, y=5):
    time.sleep(3)
    return x + y

print(1,add(4,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(4.0,5))
# 如上,每个都要算 3 秒

@lru_cache()
def add(x=[], y=5):
    time.sleep(3)
    x.append(1)
    return x
 
print(add([1]))  #  TypeError: unhashable type: 'list'

总结:lru_cache装饰器应用

使用前提:
同样的函数参数一定得到同样的结果
函数执行时间很长,且要多次执行
本质:函数调用的参数 ==> 返回值
缺点:
不支持缓存过期,key无法过期,shixiao
不支持清除操作
不支持分布式,是一个单机缓存
适用场景:单机上需要空间换时间的地方,可以用缓存来将计算编程快速查询

相关文章

Python写个迷你的出门向导,十几行代码就搞定了!旅游必备哦~

Python写个迷你的出门向导,十几行代码就搞定了!旅游必备哦~

写在前面 出门在外的朋友,你还在为了住宿以及路线而烦恼么?那么你就自己来动手写一个简单的Python代码吧!出门旅...

python 使用进程池Pool进行并发编程

进程池Pool 当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如...

python ‘%r‘或者‘{!r}‘的意思

这两个都是python的...

Python:目录和文件的操作模块os.path和OS常用方法

1、目录和文件的操作模块os.path,在使用之前要先导入:import os.path。它主要有以下几个重要的功能函数ÿ...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。