💻 Python functools
介绍
python内置库。
functools库是Python 3.5及以上版本引入的,用于支持函数式编程,旨在提高代码的可读性和可维护性,尤其在大型项目中非常有用。
常用函数
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce',
'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
'cached_property']
functools.cache / @cache
functools.cache(user_function)
在Python 3.9中,functools
模块引入了一个新的装饰器 @cache
,用于缓存函数的返回值,这可以显著提高函数的性能,特别是对于那些计算密集型或经常被重复调用的函数。
@cache
是 functools.cache
的简写形式,与 @lru_cache
类似,只不过它没有大小限制,即缓存的所有结果都会一直保留,直到程序结束或缓存被手动清除。
- 适用场景
- 计算密集型函数:如递归计算、复杂数学计算等。
- 不变函数:函数的返回值只取决于输入参数,且输入参数相同时返回值总是相同。
- 频繁调用:函数在程序运行期间被频繁调用,且输入参数有重复。
- 注意事项
- 内存消耗:由于
@cache
没有缓存大小限制,如果被缓存的函数有大量不同的输入参数,可能会导致内存使用过多。- 不适合变动函数:如果函数的返回值依赖于外部状态或参数的变化,不适合使用
@cache
。
如下demo,fibonacci
函数使用了 @cache
装饰器,因此它的返回值会被缓存。如果多次调用 fibonacci
函数,且参数相同,函数不会重新计算,而是直接返回缓存中的结果。
from functools import cache
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 输出 55
print(fibonacci(20)) # 输出 6765
清除缓存:缓存可以通过调用装饰函数的 cache_clear
方法来手动清除:
fibonacci.cache_clear()
functools.lru_cache / @lru_cache
@functools.lru_cache(user_function)
@functools.lru_cache(maxsize=128, typed=False)
functools.lru_cache
是 Python 中用于缓存函数结果的装饰器,利用了最近最少使用(LRU,Least Recently Used)缓存策略。它在处理重复计算、递归函数或需要优化性能的场景中特别有用。
- 主要参数
maxsize
: 缓存的最大条目数。如果设置为None
,缓存大小无限制。如果缓存满了,最久未使用的条目会被移除。typed
: 如果设置为True
,不同类型的参数将被分别缓存。例如,f(3)
和f(3.0)
会被视为不同的调用并分别缓存。
- 适用场景
- 计算密集型函数:如递归计算、复杂数学计算等。
- 不变函数:函数的返回值只取决于输入参数,且输入参数相同时返回值总是相同。
- 频繁调用:函数在程序运行期间被频繁调用,且输入参数有重复。
- 注意事项
- 内存消耗:缓存大小受
maxsize
限制,如果设置为None
可能会导致内存使用过多。- 不适合变动函数:如果函数的返回值依赖于外部状态或参数的变化,不适合使用
@lru_cache
。
比较
@cache
和@lru_cache
@cache
和@lru_cache
都是用于缓存的装饰器,但有一些不同点:
@cache
没有缓存大小限制,适合用于确定性函数(即总是返回相同结果的函数)。@lru_cache
允许指定缓存大小,使用最近最少使用(LRU)算法管理缓存,适合用于缓存大小需要受控的场景。
如下demo,fibonacci
函数使用了 @lru_cache
装饰器,并指定了 maxsize=128
,表示最多缓存 128 个结果。超过这个数量的缓存将按照 LRU 策略进行管理。
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 输出 55
print(fibonacci(20)) # 输出 6765
带参数的用法
如果你的函数接受不同类型的参数,并且你希望将它们分别缓存,可以使用 typed=True
:
如下demo,example_function(3)
和 example_function(3.0)
会被视为不同的调用并分别缓存。
@lru_cache(maxsize=128, typed=True)
def example_function(x):
return x
print(example_function(3)) # 调用 example_function(3),缓存结果
print(example_function(3.0)) # 调用 example_function(3.0),缓存结果
清除缓存
可以通过调用装饰函数的 cache_clear
方法来手动清除缓存:
fibonacci.cache_clear()
查看缓存信息
可以通过以下方法查看缓存的命中率和缓存的使用情况:
hits
表示缓存命中次数。misses
表示缓存未命中次数。maxsize
表示缓存的最大条目数。currsize
表示当前缓存的条目数。print(fibonacci.cache_info()) # 输出 CacheInfo(hits=9, misses=21, maxsize=128, currsize=22)
functools.partial
functools.partial(func, /, *args, **keywords)
Return a new partial
object which when called will behave like func
called with the positional arguments args
and keyword arguments keywords
. 用于创建一个新的可调用对象,该对象在调用时的行为类似于使用位置参数 args 和关键字参数 keywords 调用 func 函数。如果调用时提供了更多的参数,则它们会被附加到 args 上。如果提供了额外的关键字参数,则它们会扩展并覆盖 keywords。 -> 创建一个新的可调用对象,这个对象是原函数的一个部分应用(partial application)。也就是说,partial()可以用来固定原函数的一个或多个参数,从而得到一个新的函数。新函数的调用方式与原函数相同,只不过在调用时可以省略掉原函数被固定的参数。
等价于如下代码:
def partial(func, /, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = {**keywords, **fkeywords}
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
demo:
from functools import partial
def add(x, y):
return x + y
add_five = partial(add, 5)
print(add_five(3)) # 输出 8
functools.update_wrapper
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
用于更新包装函数 (wrapper function) 以便其看起来像被包装的函数 (wrapped function)。 它通常与 functools.wraps
一起使用,以确保装饰器函数能够正确地保留被装饰函数的元数据,如名称、模块和文档字符串。
- 主要参数
wrapper
: 需要更新的包装函数。wrapped
: 被包装的函数。assigned
: 一个元组,指定哪些属性需要从被包装函数赋值到包装函数,默认值是functools.WRAPPER_ASSIGNMENTS
,包括__name__
,__module__
, 和__doc__
。updated
: 一个元组,指定哪些属性的字典需要被更新,默认值是functools.WRAPPER_UPDATES
,通常是__dict__
。
Demo2: 手动使用 update_wrapper
如果你不使用 functools.wraps
,也可以手动调用 update_wrapper
: 在这个示例中,functools.update_wrapper(wrapper, func)
手动将 func
的元数据更新到 wrapper
上,从而保留了被包装函数的名称和文档字符串。
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
functools.update_wrapper(wrapper, func) # !!!
return wrapper
@my_decorator
def say_hello():
"""This function says hello."""
print("Hello!")
say_hello()
print(say_hello.__name__) # 输出 'say_hello'
print(say_hello.__doc__) # 输出 'This function says hello.'
@functools.wraps
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
functools.wraps
是一个装饰器,常用于定义其他装饰器时,以便保持被装饰函数的元数据。它可以将被装饰函数的属性复制到包装函数中,使得包装函数看起来更像被装饰的原始函数。
代码实现:实际就是调用了 functools.update_wrapper
函数。
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
- 主要参数
wrapped
: 被包装的函数。assigned
: 一个元组,指定哪些属性需要从被包装函数赋值到包装函数,默认值是functools.WRAPPER_ASSIGNMENTS
,包括__name__
,__module__
, 和__doc__
。updated
: 一个元组,指定哪些属性的字典需要被更新,默认值是functools.WRAPPER_UPDATES
,通常是__dict__
。
demo1: functools.wraps(func)
在这个示例中,functools.wraps(func)
实际上就是 functools.update_wrapper(wrapper, func)
的简写,它确保了 say_hello
函数的名称和文档字符串被正确地保留在 wrapper
函数中。
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator # 相当于 decorated = my_decorator(decorated)
def say_hello():
"""This function says hello."""
print("Hello!")
say_hello()
print(say_hello.__name__) # 输出 'say_hello'
print(say_hello.__doc__) # 输出 'This function says hello.'
不使用@wraps,则:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def say_hello():
"""This function says hello."""
print("Hello!")
say_hello()
print(say_hello.__name__) # 输出 'wrapper'
print(say_hello.__doc__) # 输出 'None'
再来一个例子:
import time
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
r = func(*args, **kwargs)
end = time.time()
print(func.__name__, end -start)
return r
return wrapper
@timethis
def countdown(n: int):
"""count down docstring.."""
while n > 0:
n -= 1
print(countdown.__name__)
print(countdown.__doc__)
print(countdown.__annotations__) # 存储函数参数和返回值的类型注解
# run:
countdown
count down docstring..
{'n': <class 'int'>}