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

1、functools模块---reduce()

  reduce方法:

    reduce方法,就是减少

    可迭代对象不能为空,初始值没提供就在可迭代对象总去一个元素。

1 def reduce(function, iterable, initializer=None):
2     it = iter(iterable)
3     if initializer is None:
4         value = next(it)
5     else:
6         value = initializer
7     for element in it:
8         value = function(value, element)
9     return value
reduce()实现代码

    举例1:

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

 

2、functools ----partial 方法

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

     从 partial 生成的新函数,是对原函数的封装---装饰器

1 def partial(func, *args, **keywords):
2     def newfunc(*fargs, **fkeywords):
3         newkeywords = keywords.copy()
4         newkeywords.update(fkeywords)
5         return func(*args, *fargs, **newkeywords)
6     newfunc.func = func
7     newfunc.args = args
8     newfunc.keywords = keywords
9     return newfunc
partial源代码

     举例 1:

 1 def partial(func, *args, **keywords):
 2     def newfunc(*fargs, **fkeywords):
 3         newkeywords = keywords.copy()
 4         newkeywords.update(fkeywords)
 5         return func(*args, *fargs, **newkeywords)
 6     ''' 这是什么意思?'''
 7     # newfunc.func = func
 8     # print(newfunc.func) # <function add at 0x00000000029DCF28>
 9     # newfunc.args = args
10     # print(newfunc.args) # ()
11     # newfunc.keywords = keywords
12     # print(newfunc.keywords) # {'y': 5}
13     return newfunc
14 
15 def add(x, y):
16     return x + y
17 newadd = partial(add,y=5)
18 # print(newadd(4))

 

    举例2:

 1 from functools import partial
 2 import inspect
 3 
 4 def add(x:int, y):
 5     return x + y
 6 
 7 newadd = partial(add, 4)
 8 print(inspect.signature(newadd))
 9 print(newadd(5))
10 
11 # (y)
12 # 9
13 
14 newadd = partial(add, y=5)
15 print(inspect.signature(newadd))
16 print(newadd(4))
17 
18 # (x, *, y=5) # 因为 y=5 是关键字传参,还是可以被更新,所以还是会出现的
19 # 9
20 
21 newadd = partial(add, x=4,y=5)
22 print(inspect.signature(newadd))
23 print(newadd())
24 
25 # (*, x=4, y=5)
26 # 9
27 
28 newadd = partial(add,x=4,y=5)
29 print(inspect.signature(newadd))
30 print(newadd(x=10,y=12))
31 
32 # (*, x=4, y=5)
33 # 22
34 
35 
36 # newadd = partial(add, x=5) # 报错,虽然x=5被**kwargs收了,但是 4 传入后是frags,最终赋给x,而后面有x=5,重复
37 # print(newadd(4))

 

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

 1 '''偏函数作用:把一些函数从多参---变成单参----这些函数就可以当无参装饰器'''
 2 
 3 def update_wrapper(wrapper,                             
 4                    wrapped,                                                         
 5                    assigned = WRAPPER_ASSIGNMENTS,
 6                    updated = WRAPPER_UPDATES):
 7 
 8 def wraps(wrapped,                                            @wraps(fn) 等价 w=wraps(fn)(w) 而wraps(fn)就是一个新函数      
 9           assigned = WRAPPER_ASSIGNMENTS,                       wraps函数中可以看到,传入 w,其他的都是固定值,同时返回一个偏函数
10           updated = WRAPPER_UPDATES):                           偏函数只有第一个参数没有定,假设生成一个新函数 new()
11     return partial(update_wrapper, wrapped=wrapped,
12                    assigned=assigned, updated=updated)          这个new() 只需传入 update_wrapper参数即可,当传入后,update_wrapper
13                                                                 只剩wrapped这个参数, w=wraps(fn)(w)这个第二个参数就是w
14 
15 from functools import update_wrapper,wraps
16 
17 def logger(fn):
18     @wraps(fn) # w=wraps(fn)(w)结构就是一个柯里化的样式
19     def w(*args, **kwargs):
20         ''' this is wrapper function'''
21         print('执行业务功能之前')
22         ret = fn(*args, **kwargs)
23         print('执行业务功能之后')
24         return ret
25     # update_wrapper(w, fn)
26     return w
27 
28 @logger # add = logger(add)  ----> add = wrapper
29 def add(x, y):
30     ''' this is add function'''    # 这个只能放在第一行才能打印!!!!!
31     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:

 1 from functools import lru_cache
 2 import time
 3 
 4 @lru_cache() # 带参的装饰器
 5 def add(x=[], y=1):
 6     x.append(y)
 7     return x
 8 
 9 print(add())
10 print(add()) # 第二次执行,是直接找上次的结果,并不会在追加,如果不加缓存,就会继续追加
11 
12 print(add([0])) # 不可hash,以为@lru_cache 原码影响。 

     lru_cache 源码分析:

 1 class _HashedSeq(list):
 2     """ This class guarantees that hash() will be called no more than once
 3         per element.  This is important because the lru_cache() will hash
 4         the key multiple times on a cache miss.
 5 
 6     """
 7 
 8     __slots__ = 'hashvalue'
 9 
10     def __init__(self, tup, hash=hash):
11         self[:] = tup
12         self.hashvalue = hash(tup)
13 
14     def __hash__(self):
15         return self.hashvalue
16 
17 def _make_key(args, kwds, typed,
18              kwd_mark = (object(),),
19              fasttypes = {int, str, frozenset, type(None)},
20              tuple=tuple, type=type, len=len):
21     """Make a cache key from optionally typed positional and keyword arguments
22 
23     The key is constructed in a way that is flat as possible rather than
24     as a nested structure that would take more memory.
25 
26     If there is only a single argument and its data type is known to cache
27     its hash value, then that argument is returned without a wrapper.  This
28     saves space and improves lookup speed.
29 
30     """
31     # All of code below relies on kwds preserving the order input by the user.
32     # Formerly, we sorted() the kwds before looping.  The new way is *much*
33     # faster; however, it means that f(x=1, y=2) will now be treated as a
34     # distinct call from f(y=2, x=1) which will be cached separately.
35     key = args
36     if kwds:
37         key += kwd_mark
38         for item in kwds.items():
39             key += item
40     if typed:
41         key += tuple(type(v) for v in args)
42         if kwds:
43             key += tuple(type(v) for v in kwds.values())
44     elif len(key) == 1 and type(key[0]) in fasttypes:
45         return key[0]
46     return _HashedSeq(key)
47 
48 def lru_cache(maxsize=128, typed=False):
49 
50 # 从原码可以看出,args 只能是元组,因为最终返回  _HashedSeq(key),而事实上,class _HashedSeq(list): 是元组继承而来,def __init__(self, tup, hash=hash):
51 # 而且必须可hash,所以list,set,dict都不能,
52 # 对传入的参数,位置参数赋值给key,之后会添加一个 object() 形成一个新的 tuple,再将关键字参数,形成的字典,以元组形式与之前的
53 # 的元组相加,形成新的元组,最后加一个type 即元素的类型。
54 # 如下:
55 # from functools import  _make_key
56 # key = _make_key((1,2,3),{'a':1},False)
57 # print(key) # [1, 2, 3, <object object at 0x000000000019E160>, 'a', 1]
58 #
59 # print(hash(key)) # -147545184494388729
原码分析

     举例 2:

 1 from functools import lru_cache
 2 import time
 3 
 4 @lru_cache()
 5 def add(x=4, y=5):
 6     time.sleep(3)
 7     return x + y
 8 
 9 print(1,add(4,5))
10 print(2,add(4))
11 print(3,add(y=5))
12 print(4,add(x=4,y=5))
13 print(5,add(y=5,x=4))
14 print(6,add(4.0,5))
15 # 如上,每个都要算 3 秒
16 
17 @lru_cache()
18 def add(x=[], y=5):
19     time.sleep(3)
20     x.append(1)
21     return x
22  
23 print(add([1]))  #  TypeError: unhashable type: 'list'

      总结:lru_cache装饰器应用 

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

 

posted @ 2018-08-28 22:54  JerryZao  阅读(476)  评论(0编辑  收藏  举报