每日一包 - functools

330 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

介绍

functoolsPython 内置的模块,通常情况下functools模块中的功能适用于所有可调用的对象。该模块定义了很多方法,这些方法通常都是被当做装饰器来使用的。本文主要介绍以下三种方法的使用:

  • wraps
  • cache
  • cached_property

使用

wraps

该装饰器通常用在我们定义装饰器时使用,装饰器的目的就是为了给被装饰对象添加新的功能,但是不能修改被装饰对象的源代码和调用方式,装饰器的原理其实就是偷梁换柱,但是单纯的使用装饰器存在一点点的小瑕疵,比如下述代码示例:

当打印被装饰过的函数的函数名或者被装饰函数有说明文档的时候,如果打印出来就会暴露调用的函数并不是原函数了。

import time
​
def timer(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        print('hello')
        time.sleep(2)
        end = time.time()
        print(end-start_time)
        res = func(*args,**kwargs)
​
​
        return res
    return wrapper
​
@timer
def index(x,y):
    '''
    doc:我是index
    :param x:
    :param y:
    :return:
    '''
    print(x,y)
​
index(1,2)
​
print(index.__name__) # wrapper
print(index.__doc__) # None'''
原函数有说明文档,被装饰后没有了
原函数的函数名是index,只要已打印函数的名字属性,就会暴露不是原函数的事实
'''

解决上述问题有两种方案:

  • 方式一 - 手动将原函数的属性赋值给wrapper函数

使用这种方式有一个非常明显的缺点,就是每个函数的属性有很多,难道真的要一条一条的进行修改吗?

# 1、函数wrapper.__name__ = 原函数.__name__
# 2、函数wrapper.__doc__ = 原函数.__doc__
​
wrapper.__name__ = func.__name__
​
wrapper.__doc__ = func.__doc__
  • 方式二 - 使用wraps装饰器

wraps本质也是一个装饰器,会将原函数的属性全部赋值给wrapper函数,实现完美的偷梁换柱

from functools import wraps
def outer(func):
    # 将被装饰函数当做参数传给wraps
    @wrapper(func)
    def wrapper(*args,**kwargs)
        res = func(*args,**kwargs)
        return res
    return wrapper

cache

缓存功能,当函数被cache装饰之后,调用函数后得到的结果会被存放在缓存中,下次调用该函数并且返回值相等时就不会重复执行该函数,而是在缓存中获取该值。

from functools import cache
​
@cache
def factorial(n):
    return n * factorial(n-1) if n else 1>>> factorial(10)      # 没有缓存结果,递归调用11次
3628800
>>> factorial(5)       # 结果已经存在缓存中,只需要在缓存中获取结果,无需在执行函数获取结果
120
>>> factorial(12)      # 只需要递归调用两次,前10次结果已经存放在缓存中
479001600

cached_property

cached_property 是一个装饰器,作用是将方法转换为特征属性,一次性的计算该方法的值之后,将其存入缓存中,调用方法是会被当做一个属性进行调用,在该值不可变的高计算资源消耗的情况下该装饰器非常有用,因为只要计算过一次之后,就会将结果直接保存,再次使用时直接从缓存中取出即可。

如果不需要该缓存值,可以通过delattr进行属性删除,就可以再次调用装饰器装饰的方法了。

from functools import cached_property
​
class DataSet:
​
    def __init__(self, sequence_of_numbers):
        self._data = tuple(sequence_of_numbers)
​
    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

同样的我们可以通过cacheproperty这两个装饰器实现和cached_property装饰器类似的效果:

from functools import cached
​
class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers
​
    @property
    @cache
    def stdev(self):
        return statistics.stdev(self._data)

总结

本文主要介绍了functools模块的三个方法,结合我们实际代码需求进行灵活的选择即可,functools模块下的方法非常多,基本上都是为可调用对象服务的,有兴趣的小伙伴可以在官方网站上了解更多该模块的方法。