Python-装饰器的灵活实现:带参数与不带参数

作者:数据人阿多   日期:2026年2月4日

背景

装饰器是Python中一种强大的语法糖,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个可调用对象,它接受一个函数作为输入,并返回一个新的函数。装饰器的使用方式非常灵活,既可以不带参数使用,也可以带参数使用

小编环境

import sys

print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.11

函数装饰器实现

我们先来看一个简单的函数装饰器实现:

import time

def delayed_start(func=None, *, duration=1):
    def decorator(_func):
        def wrapper(*args, **kwargs):
            print(f"Wait for {duration} seconds before starting...")
            time.sleep(duration)
            return _func(*args, **kwargs)
        return wrapper

    if func is None:
        return decorator
    else:
        return decorator(func)

这个装饰器可以以两种方式使用:

  1. 不带参数使用:当装饰器不带参数使用时,Python会直接将装饰的函数作为参数传递给 delayed_start
@delayed_start
def hello_no_arg(name="datashare"):
    print("from hello_no_arg, param name =", name)

等价于:

hello_no_arg = delayed_start(hello_no_arg)
  1. 带参数使用:当装饰器带参数使用时,Python会先调用装饰器函数,返回一个真正的装饰器,然后再用这个装饰器装饰函数
@delayed_start(duration=2)
def hello_with_arg(name="datashare"):
    print("from hello_with_arg, param name =", name)

等价于:

hello_with_arg = delayed_start(duration=2)(hello_with_arg)

实现原理

delayed_start函数的巧妙之处在于它通过检查 func参数是否为 None来判断装饰器的使用方式:

  • 如果 func不是 None,说明是不带参数使用,直接返回 decorator(func)
  • 如果 funcNone,说明是带参数使用,返回 decorator函数等待接收真正的函数参数

类装饰器实现

类装饰器通过实现 __call__方法来实现装饰器的功能。下面是带参数和不带参数的类装饰器实现:

from functools import wraps
import time

class Timer:
    def __init__(self, func=None, *, print_args=False):
        self.func = func
        self.print_args = print_args

    def __call__(self, *args, **kwargs):
        # 情况 1:@Timer(print_args=True)
        # 第一次 __call__,args[0] 是函数
        if self.func is None:
            func = args[0]
            return self._decorate(func)

        # 情况 2:@Timer
        # 或者已经绑定好函数,真正执行
        return self._decorate(self.func)(*args, **kwargs)

    def _decorate(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            st = time.perf_counter()
            ret = func(*args, **kwargs)

            if self.print_args:
                print(f'"{func.__name__}", args: {args}, kwargs: {kwargs}')

            print(f"time cost: {time.perf_counter() - st:.4f} seconds")
            return ret

        return wrapper
  1. 不带参数使用
@Timer
def compute(x):
    time.sleep(1)
    return x * 2

当类装饰器不带参数使用时,Python会创建 Timer类的实例,并将 compute函数传递给 __init__方法。此时 self.func就是 compute函数。当我们调用 compute(10)时,实际上是调用 Timer实例的 __call__方法

  1. 带参数使用
@Timer(print_args=True)
def compute2(x):
    time.sleep(1)
    return x * 3

当类装饰器带参数使用时,Python会先调用 Timer(print_args=True)创建实例,此时 self.funcNone。然后用这个实例去装饰 compute2函数,这相当于调用 Timer实例的 __call__方法,并将 compute2作为参数传入

两种实现方式的比较

特性函数装饰器类装饰器
代码简洁性更简洁,适合简单的装饰逻辑更复杂,但结构更清晰
状态管理需要使用闭包或nonlocal变量可以使用实例属性,更直观
可扩展性适合简单的装饰功能适合需要维护状态的复杂装饰器
可读性对于简单场景更易读对于复杂场景更易维护

实际应用场景

  1. 性能监控:如 Timer装饰器,用于测量函数执行时间
  2. 权限验证:检查用户是否有权限执行某个函数
  3. 日志记录:自动记录函数的调用和参数
  4. 缓存:实现函数结果的缓存,提高性能
  5. 重试机制:当函数执行失败时自动重试

最佳实践

  1. 使用 functools.wraps装饰器来保留原函数的元数据(如函数名、文档字符串等)
  2. 对于简单的装饰器,优先使用函数装饰器
  3. 对于需要维护状态的装饰器,考虑使用类装饰器
  4. 在设计带参数的装饰器时,确保同时支持带参数和不带参数的使用方式

总结

Python装饰器是一个强大而灵活的特性,它允许我们以非侵入式的方式增强函数的功能。通过掌握函数装饰器和类装饰器的实现方式,以及如何实现带参数和不带参数的装饰器,我们可以编写出更加通用和可重用的代码。无论是简单的函数增强还是复杂的状态管理,装饰器都能提供优雅的解决方案

理解装饰器的工作原理不仅有助于我们编写更好的装饰器,还能加深我们对Python函数式编程和元编程的理解,是成为高级Python开发者的重要一步

历史相关文章


以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货

微信公众号 QQ群