【python标准库】高手分界线:functools

cache

递归是一种十分方便的编程方法,但便捷带来的是内存爆炸的风险。

比如搞一个斐波那契数列并计时,有关time的解析请看这里:一文撸懂python内置的时间模块

>>> fib = lambda n : n if n<2 else fib(n-1)+fib(n-2)
>>> def test(n):
...   t = time.time()
...   fib(n)
...   print(time.time()-t)
...
...
>>> test(25)
0.025918960571289062
>>> test(30)
0.38434386253356934
>>> test(35)
3.017500877380371
>>> test(40)
34.15391778945923

这甚至还没我笔算快。

原因很简单,每次递归都得调用两次自己,也就是说,调用函数的次数呈指数增长——这要是不爆炸就有鬼了。

笔算之所以快,是因为把底层的计算结果存留在了纸上。cache的用途与此相似,是把未命名函数缓存下来,这样就不必没完没了地重复计算。

import functools
@functools.cache
def fib(n):
    return fib(n-1)+fib(n-2) if n > 1 else n
fib(40)
# 102334155 这个结果很快就得出了
def test(n):
    t = time.time()
    fib(n)
    print(time.time()-t)

test(40)
#0.0
test(400)
#0.0

函数修饰器

@是一种语法糖,名为函数修饰器,在上文中,通过cache来修饰fib,则每次调用fib时,实则把fib放到cache中执行。或者可以把fib理解为cache的入口函数——每次调用fib就相当于进入了cache

为了深入理解这种关系,咱们可以把test改成一个修饰器。

def getTime(func):
    def wrapper(*args,**kwargs):
        t = time.time()
        func(*args,**kwargs)
        print(time.time()-t)
    return wrapper
@getTime
def Fib(n):
    print(fib(n))
Fib(55)
#139583862445    #执行Fib中print的结果
#0.0             #getTime中print的结果

wraps

使用修饰器的一个尴尬的结果是,我们要用的函数成为了一个入口,而真正执行的函数是修饰器返回的那个,二者之间的名字是不同的。

>>> Fib.__name__
'wrapper'           #这个wrapper是getTime返回的函数的名字

update_wrapper函数则可以更新函数的各种信息,而wraps为其装饰器模式。

def getTime(func):
    @functools.wraps(func)
    def wrapper(*args,**kw):
        t = time.time()
        func(*args,**kw)
        print(time.time()-t)
    return wrapper

@getTime
def Fib(n):
    print(fib(n))

print(Fib.__name__)
# 'Fib'

lru_cache

查看源码会发现,cache的实现方法很简单,只是调用了lru_cache

def cache(user_function, /):
    return lru_cache(maxsize=None)(user_function)

相对于cache,lru_cache提供了两个参数,一个是maxsize,代表最大缓存,默认为128;typed则是类型开关,为True时,不同类型的数据将分别缓存。

singledispatch

由于Python中不支持函数重载,所以如果我想print一些东西的话大致就得这样写

def printSth(sth):
    if type(sth)==int:
        print(sth)
    elif type(sth)==list:
        for i in sth : print(i)

singledispatch则为这种泛型提供了一种类似重载的写法:

@functools.singledispatch
def printSth(sth):
    pass

@printSth.register
def _(sth:int):
    print(sth)

@printSth.register
def _(sth:list):
    for i in sth : print(i)

其中registersingledispatch中定义的函数,其第一个参数是一种类型,若想实现上述功能,也可以采用另一种写法

@printSth.register(dict)
def _(sth):
    for key in sth : print(key,sth[key])

调用一下

>>> printSth({"a":1,"b":2,"c":3})
a 1
b 2
c 3

reduce

除了一系列修饰器之外,functools模块还提供了两个用起来很方便的函数。

functools.reduce(func,lst),是对lst中的元素,从左到右依次调用func函数,其中ffunc必须有两个参数。

>>> def add(x,y) : return x+y
...
>>> functools.reduce(add,[1,2,3,4])
10
参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

微小冷

请我喝杯咖啡

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值