这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
Python标准库包括许多强大的模块,可以让你的代码既干净又高效,今天我们介绍的是functools模块,这个模块提供了许多有用的高阶函数,我们可以利用这些高阶函数来实现缓存功能、重载、创建装饰器等,会然我们的代码更加高效,好了,让我们来看一下,functools模块都给我们提供了什么呢。
缓存
functools模块中提供了一个简单却又强大的缓存函数(装饰器)lru_cache(),lru_cache()可以直接以语法糖@的形式作用于我们的函数至上,为我们的函数提供缓存的功能,遵循最近最少使用的原则,将函数执行结果缓存,方便下次函数执行前查询,如果函数的参数比较固定,逻辑比较固定,那么将非常必要,可以有效的减少函数的执行时间。
import functools
import time
@functools.lru_cache(maxsize=32)
def add(a, b):
print("sleep 2s")
time.sleep(2)
return a+b
print("第1次调用:", add(4, 5)) # 第一次调用,无缓存,执行原函数,结果加入缓存
print("第2次调用:", add(4, 5)) # 第二次调用,参数与第一次相同,匹配到缓存,直接返回缓存,不执行原函数
print("第3次调用:", add(5, 5)) # 第三次调用,参数与之前不同,无缓存,执行原函数,结果加入缓存
print(add.cache_info()) # 检查函数的缓存信息,它显示缓存命中和未命中的数量
复制代码
执行结果为:
sleep 2s
第1次调用: 9
第2次调用: 9
sleep 2s
第3次调用: 10
CacheInfo(hits=1, misses=2, maxsize=32, currsize=2)
Process finished with exit code 0
复制代码
在这个例子中,我们使用@lru_cache装饰器处理add()函数并缓存执行的结果(可以缓存32个结果)。为了查看缓存是否真的有效,我们使用了cache_info()方法检查函数的缓存信息,它显示缓存命中和未命中的数量,通过结果我们可以看出,在第一次调用的时候并无缓存,add()函数执行结果存入缓存,第二次的时候命中缓存,第三次的时候没有命中缓存。
如果你想要更细粒度的缓存,那么你也可以包含可选的typed=true参数,这使得不同类型的参数被分别缓存。
例如,add(4.0, 5.0)和add(4, 5)将被视为不同的调用。
其实在Python3.8的版本之后,还有另一个可以用于缓存的装饰器叫做cached_property,这个函数用于缓存类属性的结果,如果你的类的属性是而又不可变的,可以使用这个。
比较
您可能已经知道,可以使用__lt__()、__gt__()和__eq__()在Python中实现比较操作符,如<、>或==。但是实现__lt__()、__gt__()、__eq__() 、__le__()、__ge__()可能是相当繁琐的,刚好functools模块包含total_ordering‘装饰器,它可以帮助我们做到这一点。
我们只需要实现__eq__(),和__lt__()、__gt__()、__le__()、__ge__()中的任一个方法,其他的它会帮我们自动提供:
from functools import total_ordering
@total_ordering
class MyNumber:
def __init__(self, value):
self.value = value
def __gt__(self, other):
return self.value > other.value
def __eq__(self, other):
return self.value == other.value
print(MyNumber(5) > MyNumber(3))
# True
print(MyNumber(1) < MyNumber(5))
# True
print(MyNumber(5) >= MyNumber(5))
# True
print(MyNumber(5) <= MyNumber(2))
# False
复制代码
很明显,它可以帮助我们减少代码和提高可读性。
partial函数(偏函数)
这里的偏函数和数学意义上的偏函数不是一个概念,functools模块中的partial函数的作用是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会比调用原来的函数更简单。理解起来是不是有些困惑,让我们来看一些实际的例子。
在Python中,int()函数可以把指定数字字符串转换为整数,默认是做十进制的转换:
print(int(1234))
# 1234
复制代码
但int()函数还提供了一个base参数,默认值为10。如果传入base参数,就可以做N进制的转换,即,默认数字字符串的进制是N进制,要转化为10进制,例如:
print(int("1234", base=8))
# 默认1234位8进制的数字,转化为10进制:668
print(int("1234", base=16))
# 默认1234位16进制的数字,转化为10进制:4660
复制代码
假设要转换大量的8进制字符串,每次都传入int(x, base=8)显得过于麻烦,于是,我们想到,可以定义一个int8()的函数,默认把base=8传进去:
def int8(x, base=8):
return int(x, base)
print(int8("1234"))
复制代码
这样,就非常方便了。
其实functools.partial的功能就是能够创建一个偏函数,不需要我们自己定义int8(),可以直接使用下面的代码创建一个新的函数int8:
int2 = functools.partial(int, base=8)
print(int2('1234'))
复制代码
所以简单来说,functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,使得我们调用这个新函数会更简单。
重载
我们都知道严格意义上来说,在Python中函数重载是不可能实现的,但实际上有一个简单的方法来实现和函数重载相似的功能,那就是使用functools模块中singledispatch函数装饰器。下面来看个例子:
@singledispatch
def show(obj):
print(obj, type(obj), "obj")
@show.register(str)
def _(text):
print(text, type(text), "str")
@show.register(int)
def _(n):
print(n, type(n), "int")
show(1234)
show("abcd")
show({"abcd": 1234})
复制代码
结果为:
1234 <class 'int'> int
abcd <class 'str'> str
{'abcd': 1234} <class 'dict'> obj
Process finished with exit code 0
复制代码
可见为show()函数传递不同的类型参数,就调用不同的函数,表现不同的行为,实际上singledispatch实现的是单泛函数,给函数加上.register方法,该方法支持绑定一个变量类型和一个函数。然后它返回一个被重载了的函数。当输入值的类型是通过.register绑定的类型时,就调用同时绑定的函数。
wraps
这一部分在我之前的文章有提到,感性趣的可以看一下:内置装饰器@functools.wrap
总结
functools模块提供了许多有用的功能和装饰器,可以帮助我们构建更好的代码,我这里提到的只是部分内容。感兴趣的小伙伴可以查看官方文档functools — Higher-order functions and operations on callable objects
最后,感谢女朋友在工作和生活中的包容、理解与支持 !





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)