杰瑞科技汇

Python decorator模块的核心功能是什么?

Python 中并没有一个独立的、名为 decorator 的标准库模块,当我们谈论“装饰器模块”时,通常指的是 functools 模块,它提供了许多用于创建和操作装饰器的实用工具函数。

Python decorator模块的核心功能是什么?-图1
(图片来源网络,侵删)

functools 模块是 Python 标准库的一部分,它提供了用于高阶函数的操作,这些函数作用于或返回其他函数(或可调用对象)。functools 中的工具对于编写更高级、更优雅的装饰器至关重要。

下面,我将从基础到高级,系统地介绍 functools 中与装饰器相关的核心功能。


functools.wraps - 装饰器的“救星”

这是 functools 模块中最著名、最重要的一个函数,如果你只记住一个,那一定是它。

问题背景

当我们编写一个简单的装饰器时,通常会这样写:

Python decorator模块的核心功能是什么?-图2
(图片来源网络,侵删)
def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Wrapper function's docstring."""
        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(name):
    """Greet someone."""
    print(f"Hello, {name}!")
say_hello("Alice")

这个装饰器能正常工作,但它有一个严重的问题:元信息丢失

print(say_hello.__name__)  # 输出: wrapper
print(say_hello.__doc__)   # 输出: Wrapper function's docstring.

say_hello 函数的 __name____doc__ 属性被 wrapper 函数的覆盖了,这在调试、文档生成(如 Sphinx)或任何依赖函数元信息的场景下会造成极大的困扰。

functools.wraps 的解决方案

functools.wraps 本身也是一个装饰器,它的作用是“复制”被装饰函数的元信息(如 __name__, __doc__, __module__ 等)到装饰器内部的 wrapper 函数上。

让我们用 wraps 来修复上面的代码:

from functools import wraps
def my_decorator(func):
    @wraps(func)  # <-- 关键在这里
    def wrapper(*args, **kwargs):
        """Wrapper function's docstring."""
        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(name):
    """Greet someone."""
    print(f"Hello, {name}!")
say_hello("Alice")
print(say_hello.__name__)  # 输出: say_hello
print(say_hello.__doc__)   # 输出: Greet someone.

say_hello 的元信息被正确地保留了。最佳实践是:在编写任何装饰器时,都在内部的 wrapper 函数上使用 @wraps(func)


functools.partial - 创建“预填充”参数的函数

partial 用于“冻结”一个函数的部分参数,从而创建一个新的、参数更少的函数,这在某些场景下非常有用,比如创建回调函数或简化函数调用。

基本用法

functools.partial(func, *args, **keywords)

  • func: 要包装的函数。
  • *args: 要固定的位置参数。
  • **keywords: 要固定的关键字参数。

示例:

假设有一个通用的函数,用于计算两个数的乘积。

from functools import partial
def multiply(a, b):
    return a * b
# 创建一个专门用于乘以 2 的新函数
double = partial(multiply, 2)
# 创建一个专门用于乘以 10 的新函数
times_ten = partial(multiply, b=10)
print(double(5))      # 输出: 10 (相当于 multiply(2, 5))
print(times_ten(5))    # 输出: 50 (相当于 multiply(5, b=10))

partial 作为装饰器

partial 也可以用作装饰器,这相当于在函数定义时就“预填充”了部分参数。

示例:

想象一个日志记录函数,它接收一个消息和日志级别。

from functools import partial
import logging
def log(level, message):
    logging.log(level, message)
# 预填充 level=logging.INFO,创建一个 info_log 函数
info_log = partial(log, level=logging.INFO)
# 现在可以直接调用 info_log,无需再指定 level
info_log("This is an info message.")
# 使用 @ 语法糖
@partial(log, level=logging.WARNING)
def warn_user(message):
    # 这个函数在调用时,log 函数的 level 已经被固定为 WARNING
    # 只需传入 message 参数
    pass
warn_user("This is a warning message.")

实际应用场景: 在 GUI 编程中,你可能需要将一个带有多个参数的函数绑定到按钮的 command 事件上。partial 可以让你轻松地传递额外的参数。


functools.lru_cache - 缓存计算结果

lru_cache 是一个非常实用的装饰器,它可以为函数提供缓存(memoization)功能,它会记住最近调用过的函数参数及其对应的返回结果,当再次使用相同的参数调用函数时,它会直接从缓存中返回结果,而无需重新执行函数体,这对于计算成本高昂的函数尤其有用。

lru_cache 的工作原理

LRU 是 "Least Recently Used"(最近最少使用)的缩写,当缓存满了之后,它会丢弃最久未被使用的结果。

基本用法

from functools import lru_cache
import time
@lru_cache(maxsize=None) # maxsize=None 表示缓存大小没有限制
def slow_function(n):
    time.sleep(2)  # 模拟一个耗时操作
    return n * n
print("Calling slow_function(10)...")
start_time = time.time()
result1 = slow_function(10)
end_time = time.time()
print(f"Result: {result1}, Time taken: {end_time - start_time:.2f}s")
print("\nCalling slow_function(10) again (should be much faster)...")
start_time = time.time()
result2 = slow_function(10) # 这次会从缓存中获取
end_time = time.time()
print(f"Result: {result2}, Time taken: {end_time - start_time:.2f}s")
print("\nCalling slow_function(20)...")
start_time = time.time()
result3 = slow_function(20) # 新的参数,会重新计算
end_time = time.time()
print(f"Result: {result3}, Time taken: {end_time - start_time:.2f}s")

lru_cache 的参数

  • maxsize: 缓存中存储的最大条目数,如果设置为 None,则缓存大小无限制,如果设置为 0,则禁用缓存,一个合理的值(如 128)通常是最佳实践。
  • typed: 如果设置为 True,不同类型的参数会被分别缓存。f(3)f(3.0) 会被视为两个不同的调用,默认为 False

lru_cache 的实用属性

lru_cache 装饰的函数会添加一些有用的属性:

  • cache_info(): 返回一个命名元组,显示缓存命中率等信息。
  • cache_clear(): 清空缓存。

示例:

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 第一次计算
print(fibonacci(10)) # 第二次,从缓存获取
print(fibonacci.cache_info())
# 输出类似: CacheInfo(hits=18, misses=11, maxsize=128, currsize=11)
fibonacci.cache_clear()
print(fibonacci.cache_info())
# 输出: CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)

functools.singledispatch - 为函数创建泛型函数

Python 是动态类型语言,没有函数重载(C++/Java 中同名不同参数的多个函数)。singledispatch 提供了一种机制,让你可以根据第一个参数的类型,来分发到不同的实现函数上,这被称为单分派

基本用法

  1. @functools.singledispatch 装饰基础函数(通常只处理 object 类型,作为最后的后备方案)。
  2. 使用 @register 方法(my_function.register)来为特定类型注册新的实现。

示例:

from functools import singledispatch
import html
# 1. 定义基础函数,并使用 @singledispatch 装饰
#    这个函数将作为处理未知类型的默认实现。
@singledispatch
def format_data(data):
    return repr(data)
# 2. 使用 .register() 方法为特定类型注册实现
@format_data.register
def _(data: str):
    # 这个函数专门处理 str 类型
    return html.escape(data)
@format_data.register
def _(data: list):
    # 这个函数专门处理 list 类型
    return "<ul>" + "".join(f"<li>{format_data(item)}</li>" for item in data) + "</ul>"
@format_data.register
def _(data: int):
    # 这个函数专门处理 int 类型
    return f"<em>{data}</em>"
# --- 测试 ---
print(format_data("This is a string with <tags>.")) # 会调用 str 版本
# 输出: This is a string with &lt;tags&gt;.
print(format_data([1, 2, "hello", [3, 4]])) # 会递归调用 list, int, str 版本
# 输出: <ul><li><em>1</em></li><li><em>2</em></li><li>hello</li><li><ul><li><em>3</em></li><li><em>4</em></li></ul></li></ul>
print(format_data(42)) # 会调用 int 版本
# 输出: <em>42</em>
print(format_data(3.14)) # 没有注册 float 版本,会调用基础 object 版本
# 输出: 3.14

singledispatch 非常适合创建能够处理多种数据类型的工具函数,比如序列化、日志记录、数据转换等,它让你的代码更加清晰和符合 Pythonic。


函数/工具 主要用途 关键点
functools.wraps 保留被装饰函数的元信息 编写装饰器的必备工具,防止 __name____doc__ 等丢失。
functools.partial 固定函数的部分参数,创建新函数 用于创建回调、简化调用,也是一种装饰器。
functools.lru_cache 缓存函数结果,提高性能 适用于纯函数且计算昂贵的场景,能极大提升速度。
functools.singledispatch 根据第一个参数的类型分发函数调用 实现了类似函数重载的功能,使代码更灵活、更具扩展性。

functools 模块是 Python 高级编程的利器,掌握这些工具,特别是 wrapslru_cache,能让你写出更专业、更高效、更优雅的 Python 代码。

分享:
扫描分享到社交APP
上一篇
下一篇