Python带参数装饰器如何定义与使用?

99ANYc3cd6
预计阅读时长 23 分钟
位置: 首页 参数 正文

这可以说是 Python 装饰器进阶的一个关键点,理解它需要一层一层地剥开来看。

核心思想

要理解为什么需要带参数的装饰器。

  • 普通装饰器:它接收一个函数作为参数,并返回一个新的函数,它的作用是“包装”或“增强”原函数,一个打印日志的装饰器,它不需要任何外部配置。

    @my_decorator
    def my_func():
        # ...
  • 带参数的装饰器:我们想让装饰器本身可以接收参数,以便在装饰时进行更灵活的配置,我们想控制日志装饰器是打印 INFO 级别还是 DEBUG 级别的日志。

    # 我们想把 "DEBUG" 这个参数传递给装饰器
    @my_decorator(level="DEBUG")
    def my_func():
        # ...

看起来语法上 @my_decorator(level="DEBUG") 像是在调用一个函数,没错!这正是理解它的关键。


分步理解:从内到外

一个带参数的装饰器,实际上是一个返回装饰器函数的函数,我们来分解这个过程,假设我们有这样一个需求:创建一个可以指定前缀的日志装饰器。

第 1 步:创建一个普通装饰器(不带参数)

我们先写一个最简单的日志装饰器,它只负责在函数执行前后打印日志。

import time
def simple_log_decorator(func):
    """一个简单的日志装饰器,不带参数"""
    def wrapper(*args, **kwargs):
        print(f"--- 开始执行函数: {func.__name__} ---")
        start_time = time.time()
        result = func(*args, **kwargs) # 执行原函数
        end_time = time.time()
        print(f"--- 函数 {func.__name__} 执行完毕,耗时: {end_time - start_time:.4f} 秒 ---")
        return result
    return wrapper

使用方式:

@simple_log_decorator
def say_hello(name):
    time.sleep(0.5)
    print(f"你好, {name}!")
say_hello("Alice")

输出:

--- 开始执行函数: say_hello ---
你好, Alice!
--- 函数 say_hello 执行完毕,耗时: 0.5001 秒 ---

第 2 步:引入参数的需求

我们希望在函数执行前打印的日志可以带上一个自定义的前缀,[INFO][WARNING]

  • 错误尝试:我们不能直接在 simple_log_decorator 里加一个 prefix 参数,因为 语法后面必须跟一个可调用对象(函数),Python 会自动将被装饰的函数 (say_hello) 作为参数传给它。

    # 这么写是错误的!
    @simple_log_decorator(prefix="[INFO]") # Python 会尝试执行 simple_log_decorator("[INFO]"),然后把返回值再作为装饰器
    def say_hello(name):
        # ...

第 3 步:创建“工厂”函数

为了满足 @my_decorator(prefix="...") 这样的语法,我们需要一个工厂函数,这个函数本身接收我们想传入的参数(如 prefix),然后返回一个真正的装饰器。

我们把这个工厂函数叫做 log_decorator_with_prefix

def log_decorator_with_prefix(prefix):
    """
    这是一个“工厂函数”,它接收 prefix 参数,
    并返回一个真正的装饰器。
    """
    # 1. 内部定义真正的装饰器
    def actual_decorator(func):
        """
        这个 actual_decorator 和我们第一步的 simple_log_decorator 长得一样,
        但它现在可以访问到外层的 prefix 变量。
        """
        def wrapper(*args, **kwargs):
            print(f"{prefix} --- 开始执行函数: {func.__name__} ---")
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"{prefix} --- 函数 {func.__name__} 执行完毕,耗时: {end_time - start_time:.4f} 秒 ---")
            return result
        return wrapper
    # 2. 工厂函数返回这个真正的装饰器
    return actual_decorator

第 4 步:使用带参数的装饰器

现在我们可以这样使用了:

@log_decorator_with_prefix(prefix="[INFO]")
def say_info(name):
    time.sleep(0.3)
    print(f"这是信息: {name}")
@log_decorator_with_prefix(prefix="[WARNING]")
def say_warning(name):
    time.sleep(0.2)
    print(f"这是警告: {name}")
print("--- 调用 say_info ---")
say_info("Bob")
print("\n--- 调用 say_warning ---")
say_warning("Charlie")

输出结果:

--- 调用 say_info ---
[INFO] --- 开始执行函数: say_info ---
这是信息: Bob
[INFO] --- 函数 say_info 执行完毕,耗时: 0.3001 秒 ---
--- 调用 say_warning ---
[WARNING] --- 开始执行函数: say_warning ---
这是警告: Charlie
[WARNING] --- 函数 say_warning 执行完毕,耗时: 0.2001 秒 ---

可以看到,通过给装饰器传入不同的 prefix,我们成功地定制了日志输出。


当 Python 解释器遇到 @log_decorator_with_prefix(prefix="[INFO]") 时,它会做以下事情:

  1. 首先调用工厂函数:执行 log_decorator_with_prefix(prefix="[INFO]")
  2. 获取返回值:这个调用会返回内部的 actual_decorator 函数。
  3. 应用装饰器:然后将返回的 actual_decorator 应用到下面的 say_info 函数上,这相当于执行了 say_info = actual_decorator(say_info)

@my_decorator(arg) 的完整展开形式就是 my_func = my_decorator(arg)(my_func)


完整示例:带参数的类装饰器

装饰器不仅可以是函数,也可以是类,带参数的类装饰器同样遵循“工厂”模式。

类装饰器基本原理

一个类要作为装饰器,必须实现 __call__ 方法,当类的实例被调用时(就像函数一样),__call__ 方法就会被执行。

class SimpleClassDecorator:
    def __init__(self, func):
        print(f"装饰器初始化,正在包装 {func.__name__}")
        self.func = func
    def __call__(self, *args, **kwargs):
        print(f"类装饰器:在 {self.func.__name__} 执行前")
        result = self.func(*args, **kwargs)
        print(f"类装饰器:在 {self.func.__name__} 执行后")
        return result
@SimpleClassDecorator
def my_class_func():
    print("我是被类装饰的函数")
my_class_func()

输出:

装饰器初始化,正在包装 my_class_func
类装饰器:在 my_class_func 执行前
我是被类装饰的函数
类装饰器:在 my_class_func 执行后

带参数的类装饰器

我们想让这个类装饰器也能接收参数,我们只需要再加一层“外壳”类作为工厂即可。

class ConfigurableClassDecorator:
    def __init__(self, prefix=""):
        """
        工厂类的 __init__ 方法接收装饰器的参数。
        这个实例不会被直接调用,而是作为最终的装饰器。
        """
        self.prefix = prefix
        print(f"工厂类已创建,prefix 为: '{self.prefix}'")
    def __call__(self, func):
        """
        这个 __call__ 方法才是真正的装饰器逻辑。
        它接收被装饰的函数 func。
        """
        print(f"正在用配置 '{self.prefix}' 装饰函数 {func.__name__}")
        def wrapper(*args, **kwargs):
            print(f"{self.prefix} -> 开始执行 {func.__name__}")
            result = func(*args, **kwargs)
            print(f"{self.prefix} -> {func.__name__} 执行完毕")
            return result
        return wrapper
# 使用方式
@ConfigurableClassDecorator(prefix="[CLASS-INFO]")
def my_configurable_func():
    print("执行核心逻辑...")
print("\n--- 开始调用函数 ---")
my_configurable_func()

输出结果:

工厂类已创建,prefix 为: '[CLASS-INFO]'
正在用配置 '[CLASS-INFO]' 装饰函数 my_configurable_func
--- 开始调用函数 ---
[CLASS-INFO] -> 开始执行 my_configurable_func
执行核心逻辑...
[CLASS-INFO] -> my_configurable_func 执行完毕
类型 结构 核心思想 使用示例
普通装饰器 def decorator(func): ... 接收一个函数,返回一个新函数。 @my_decorator
带参数的函数装饰器 def decorator_factory(args): <br> &nbsp;&nbsp;def actual_decorator(func): ... <br> &nbsp;&nbsp;return actual_decorator <br> return actual_decorator 一个工厂函数,接收参数并返回一个真正的装饰器。 @my_decorator_factory(arg)
带参数的类装饰器 class DecoratorFactory: <br> &nbsp;&nbsp;def __init__(self, args): ... <br> &nbsp;&nbsp;def __call__(self, func): ... 一个工厂类,__init__接收参数,__call__作为真正的装饰器逻辑。 @DecoratorFactory(arg)

掌握了“工厂函数/类返回真正的装饰器”这一核心思想,你就能自如地使用和理解带参数的装饰器了。

-- 展开阅读全文 --
头像
Philips 190SW拆机要注意哪些细节?
« 上一篇 今天
戴尔Vostro 5560拆机后内部有何升级空间?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]