Python 之 functools 模块随行笔记
1、reduce
方法
1.1 语法
Docstring:
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
Type: builtin_function_or_method
- 可迭代对象
sequence
不能为空 - 初始值
initial
没提供就在可迭代对象中取一个元素
1.2 示例
from functools import reduce
print(reduce(lambda x,y: x + y, range(1, 5), 10)) # 20
# 10 + 1
# 11 + 2
# 13 + 3
# 16 + 4
# 20
print(reduce(lambda x,y: x * y, range(1, 5), 10)) # 240
from functools import reduce
nums = [6, 9, 1, 2, 10, 5]
print(nums)
print(sum(nums))
print(reduce(lambda val, x: val + x, nums))
print(reduce(lambda val, x: val + x, nums, 10))
[6, 9, 1, 2, 10, 5]
33
33
43
2、partial
方法(偏函数)
2.1 概念
- partial 偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回
- 从partial生成的新函数,是对原函数的封装
2.2 语法
Init signature: partial(self, /, *args, **kwargs)
Docstring:
partial(func, *args, **keywords) - new function with partial application
of the given arguments and keywords.
2.3 本质
# partial 函数本质
# 无法验证参数类型
# 函数挺简单的,大家可以解读一下,了解偏函数的本质
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords): # 包装函数
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
print(newkeywords, keywords, fkeywords, fargs)
return func(*(args + fargs), **newkeywords)
print(args, keywords)
newfunc.func = func # 保留原函数
newfunc.args = args # 保留原函数的位置参数
newfunc.keywords = keywords # 保留原函数的关键字参数
return newfunc
def add(x, y):
return x + y
foo = partial(add, 5, y=5)
print(foo())
(5,) {'y': 5}
{'y': 5} {'y': 5} {} ()
10
2.4 示例
2.4.1 示例1
import functools
import inspect
def add(x:int, y:int) -> int:
print('x = {}, y = {}'.format(x, y))
return x + y
newadd = functools.partial(add, y=5)
print(newadd(7))
print(newadd(7, y=6))
print(newadd(y=10, x=11))
print(newadd(y=6, x=6))
print(inspect.signature(newadd))
x = 7, y = 5
12
x = 7, y = 6
13
x = 11, y = 10
21
x = 6, y = 6
12
(x: int, *, y: int = 5) -> int
2.4.2 示例2
import functools
import inspect
def add(x:int, y:int, *args)->int:
print(args)
return x + y
newadd = functools.partial(add, 1, 3, 6, 5)
print(newadd(7))
print(newadd(7, 10))
print(newadd())
print(inspect.signature(newadd))
(6, 5, 7)
4
(6, 5, 7, 10)
4
(6, 5)
4
(*args) -> int
3、lru_cache
方法
3.1 概念
-
使用前提
同样的函数参数一定得到同样的结果 函数执行时间很长,且要执行多次
-
本质是函数调用的参数 => 返回值
-
缺点:
不支持缓存过期,key无法过期、失效 不支持清除操作 不支持分布式,是一个单机的缓存
-
适用场景:单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询
3.2 语法
-
Least-recently-used
装饰器,最近最少使用,cache
缓存 -
如果
maxsize
设置为None
,则禁用LRU
功能,并且缓存可以无限制增长,当maxsize
是2的幂时,LRU
功能执行得最好 -
如果
typed
设置为True
,则不同类型的函数参数将单独保存,例如,f(3) f(3.0)
将被视为具有不同结果的不同调用Signature: functools.lru_cache(maxsize=128, typed=False) Docstring: Least-recently-used cache decorator. If *maxsize* is set to None, the LRU features are disabled and the cache can grow without bound. If *typed* is True, arguments of different types will be cached separately. For example, f(3.0) and f(3) will be treated as distinct calls with distinct results.
3.3 示例
3.3.1 缓存计算结果
import functools
import time
@functools.lru_cache(typed=True)
def add(x, y, z=3):
time.sleep(2)
return x + y +z
print(add(3, 3))
print(add(3, 3))
print(add(3.0, 3.0))
9 # 等2秒出结果
9 # 立马出结果
9.0 # 等2秒出结果,如果typed=False,则不需等待2秒
3.3.2 优化斐波那契数列
-
斐波那契数列写法参考以下博文
-
其中递归且未保存执行结果的速率最低,可以考虑使用
lru_cache
缓存上去执行结果 -
可以看出以下代码执行耗时极短,使用缓存机制加速了代码的执行速度
import functools import datetime @functools.lru_cache() def fib(n): return 1 if n < 3 else fib(n-1) + fib(n-2) start = datetime.datetime.now() print([fib(i+1) for i in range(35)]) delta = (datetime.datetime.now() - start).total_seconds() print(delta)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465] 0.0
3.4 lru_cache
原理
-
通过一个字典缓存被装饰函数的调用和返回值
-
通过
functools._make_key
方法来保存输入参数functools._make_key((4, 6), {'z': 3}, False) # [4, 6, <object at 0x497e70>, 'z', 3] functools._make_key((4, 6, 3), {}, False) # [4, 6, 3] functools._make_key((4, 6, 3), {1:2}, False) # [4, 6, 3, <object at 0x1cb7e70>, 1, 2] functools._make_key((4, 6, 3), {1:2}, True) # [4, 6, 3, <object at 0x1cb7e70>, 1, 2, int, int, int, int] functools._make_key(tuple(), {'z':1, 'y':2}, False) # [<object at 0x497e70>, 'z', 1, 'y', 2] functools._make_key(tuple(), {'z':1, 'y':2}, True) # [<object at 0x1cb7e70>, 'z', 1, 'y', 2, int, int]
3.4.1 联系知识点 自定义类
-
编写一个类,可以哈希列表
class Mylist(list): def __hash__(self): return hash(tuple(self)) # hash((2, ))
l2 = Mylist() l2.append(2), l2, hash(l2) # (None, [2, 2], 3713082714462658231)
-
元组可hash,列表不可hash
print(hash(tuple([2, 2], ))) hash([2, 2])
3713082714462658231 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-38-f6f443f2d41d> in <module> 1 print(hash(tuple([2, 2], ))) ----> 2 hash([2, 2]) TypeError: unhashable type: 'list'
3.4.2 联系知识点 namedtuple
from collections import namedtuple
Point = namedtuple('Point', 'x y z')
print(Point)
p1 = Point(4, 5, 6)
print(p1)
<class '__main__.Point'>
Point(x=4, y=5, z=6)