You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python类装饰器简写形式中为__call__传入*args和**kwargs

嘿,这个问题我之前踩过坑,类装饰器带参数确实比普通函数装饰器要绕一点点,但理清楚逻辑就很简单了,咱们一步步来拆解~

1. 带参数的类装饰器实现思路

带参数的类装饰器本质是一个装饰器工厂:先给它传参数,它返回一个真正能装饰函数的可调用对象(也就是装饰器类的实例)。直接上代码示例,结合例子讲更清楚:

import functools

class MyDecorator:
    # __init__ 用来接收装饰器的参数,只在创建实例时执行一次
    def __init__(self, prefix, suffix):
        self.prefix = prefix
        self.suffix = suffix
        print(f"装饰器实例初始化,参数:prefix={prefix}, suffix={suffix}")

    # __call__ 用来接收被装饰的函数,返回包装后的函数
    def __call__(self, func):
        # 用 functools.wraps 保留原函数的元信息(比如函数名、文档字符串)
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 这里可以使用 __init__ 中保存的参数
            print(f"[{self.prefix}] 准备执行函数 {func.__name__}")
            result = func(*args, **kwargs)
            print(f"[{self.suffix}] 函数执行完成,结果:{result}")
            return result
        return wrapper

2. 两种等价的使用方式

方式一:直接用 @装饰器类(参数) 装饰

这是最常用的写法,语法简洁直观:

@MyDecorator("日志前缀", "日志后缀")
def add(a, b):
    """计算两个数的和"""
    return a + b

# 调用函数时,会触发 wrapper 的逻辑
add(3, 5)

方式二:先创建装饰器实例,再用实例装饰

就是你提到的步骤:先创建实例(此时__init__执行,参数传入并保存),再用这个实例去装饰函数:

# 先创建装饰器实例,__init__ 在这里执行一次
my_decorator = MyDecorator("日志前缀", "日志后缀")

# 用实例装饰函数,此时触发 __call__ 方法,接收 add 函数
@my_decorator
def add(a, b):
    return a + b

add(3, 5)

3. 关键逻辑梳理

  • 当你写@MyDecorator("日志前缀", "日志后缀")时,Python会先执行MyDecorator("日志前缀", "日志后缀"),创建一个装饰器实例(此时__init__被调用,参数被保存为实例属性);
  • 接着,这个实例的__call__方法会被调用,参数是被装饰的add函数;
  • __call__内部定义的wrapper函数就是最终替代原add的函数,调用add()其实是调用wrapper(),而wrapper可以随时访问实例中保存的装饰器参数。

4. 常见误区:区分带参数和不带参数的类装饰器

很多人会搞混这两种写法,这里做个简单对比:

  • 不带参数的类装饰器:__init__直接接收被装饰的函数,__call__负责包装调用逻辑:
    class SimpleDecorator:
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print("执行前")
            result = self.func(*args, **kwargs)
            print("执行后")
            return result
    
    @SimpleDecorator
    def hello():
        print("hello world")
    
  • 带参数的类装饰器:__init__接收装饰器参数,__call__接收被装饰函数,这是核心区别。

5. 额外优化:保留原函数元信息

前面的示例中用到了functools.wraps(func),这个装饰器会把原函数的__name__、__doc__等元信息复制到wrapper上,避免调用add.__name__得到的是wrapper这种尴尬情况,建议养成习惯加上。

内容的提问来源于stack exchange,提问作者user17911

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:53:04