Python 之 functools 模块随行笔记

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

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

    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)
上一篇:python 10的阶乘怎么算?


下一篇:Java排序(一)