Python 之 functools 模块(reduce/partial/lru_cache/_make_key)

本文详细介绍了Python的functools模块,包括reduce函数的语法和示例,偏函数partial的概念、语法及应用场景,以及lru_cache装饰器的原理和优化斐波那契数列的实战。利用lru_cache可以实现缓存计算结果,提高代码执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

2.4.3 示例3

# partial 伪代码,执行原理
def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy() # {}
        newkeywords.update(fkeywords) # {'y':5, 'z':6}
        return func(*args, *fargs, **newkeywords) # add(4, 4, y=5, z=6)
    newfunc.func = func # add
    newfunc.args = args # (4, )
    newfunc.keywords = keywords # {}
    return newfunc
def add(x, y, z):
    return x + y + z

newfunc = partial(add, 4)
inspect.signature(newfunc) # (*fargs, **fkeywords)
newfunc(4, y=5, z=6) # add() got multiple values for argument 'y'   报错,具体分析见上,y被重复赋值
print(newfunc.func, newfunc.args, newfunc.keywords) # <function add at 0x00000222FAC604C0> (4,) {}
print(hex(id(add)))  # func 和 add 是同一个函数 # 0x222fac604c0

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 优化斐波那契数列

  • 斐波那契数列写法参考以下博文

    Python 斐波那契数列 三种函数写法比较(递归函数的示例)

  • 其中递归且未保存执行结果的速率最低,可以考虑使用 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)

3.4.3 联系知识点 _make_key

  • _make_key代码,可以学习一下里面所用的技巧
def _make_key(args, kwds, typed,
             kwd_mark = (object(),),
             fasttypes = {int, str},
             tuple=tuple, type=type, len=len):
    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)

3.4.4 联系知识点 partial 和 柯里化

def add(x):
    def _add(y):
        return x + y
    return _add

add(2)(3)  # 5
import functools

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

newadd = functools.partial(add, 5)

newadd(6)  # 11
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值