python-带参数和不带参数的装饰器


一、不带参数的装饰器(两层嵌套)

1. 基本结构

python 复制代码
def decorator(func):
    def wrapper(*args, **kwargs):
        # 前置操作
        result = func(*args, **kwargs)
        # 后置操作
        return result
    return wrapper

2. 使用语法

python 复制代码
@decorator
def say_hello():
    print("hello")

3. 执行步骤拆解

第 1 步:Python 解释器解析 @decorator

  • 看到 @decorator,它不是一个函数调用 ,而是一个装饰器引用
  • 此时要求 decorator 是一个可调用对象(通常是一个函数),并且它只接受一个参数(即下面的被装饰函数)。

第 2 步:将被装饰函数 say_hello 作为参数传递给 decorator

  • 相当于执行:decorator(say_hello)
  • 进入 decorator 函数体:
    • 形参 func 指向原始的 say_hello 函数对象。
    • decorator 内部定义一个新函数 wrapper(此时 wrapper 还没有被执行,只是被定义)。
    • wrapper 内部会调用 func(即原始的 say_hello)。
    • decorator 返回 wrapper 函数对象。

第 3 步:替换函数名

  • 原来 say_hello 这个名字,现在被重新绑定到 decorator 的返回值,也就是 wrapper 函数。
  • 等效代码:say_hello = decorator(say_hello)

第 4 步:调用被装饰后的函数

  • 当你写 say_hello() 时,实际执行的是 wrapper() 函数。
  • wrapper 内部先执行前置代码,然后调用原始的 func(即原来的 say_hello),再执行后置代码,最后返回结果。

4. 调用时序图(不带参数)

ruby 复制代码
定义阶段:
    def decorator(func):          # 1. 定义装饰器函数
        def wrapper(*args,**kwargs):
            ...
        return wrapper

    @decorator                    # 2. 装饰器生效
    def say_hello(): ...          # 3. 定义原函数

运行时:
    say_hello()                   # 4. 实际调用 wrapper()
        -> wrapper() 内:
            -> 前置操作
            -> 原 say_hello()
            -> 后置操作

二、带参数的装饰器(三层嵌套)

1. 基本结构

python 复制代码
def decorator_with_args(param1, param2):
    def actual_decorator(func):
        def wrapper(*args, **kwargs):
            # 可以使用 param1, param2
            result = func(*args, **kwargs)
            # 也可以使用 param1, param2
            return result
        return wrapper
    return actual_decorator

2. 使用语法

python 复制代码
@decorator_with_args('hello', 42)
def say_hello():
    print("hello")

3. 执行步骤拆解(非常重要,分清楚三层)

第 1 步:Python 解析 @decorator_with_args('hello', 42)

  • 注意这里有括号 ,表示立即调用 decorator_with_args 函数,并传入参数 'hello'42
  • 所以 @ 后面跟的是一个函数调用表达式,而不是简单的函数名。

第 2 步:执行外层函数 decorator_with_args('hello', 42)

  • 进入 decorator_with_args 函数体:
    • 形参 param1='hello', param2=42
    • 在函数内部定义 一个内层函数 actual_decorator(它只接受一个 func 参数)。
    • decorator_with_args 返回 actual_decorator 函数对象。

此时,@ 后面的表达式计算结果是一个函数 actual_decorator

等效于:temp_decorator = decorator_with_args('hello', 42)

第 3 步:将被装饰函数 say_hello 传给 actual_decorator

  • 现在 @actual_decorator(注意这里 actual_decorator 是上一步返回的)会执行:actual_decorator(say_hello)
  • 进入 actual_decorator 函数体:
    • 形参 func 指向原始的 say_hello
    • 在内部定义 wrapper 函数(它可以访问外层的 param1, param2,因为闭包)。
    • actual_decorator 返回 wrapper 函数对象。

第 4 步:替换函数名

  • say_hello = actual_decorator(say_hello)
    实际上等价于:say_hello = decorator_with_args('hello',42)(say_hello)
  • 现在 say_hello 指向 wrapper

第 5 步:调用被装饰后的函数

  • say_hello() → 执行 wrapper()
  • wrapper 内部可以使用 param1param2,也可以调用原始的 func(原 say_hello)。

4. 调用时序图(带参数)

python 复制代码
定义阶段:
    def decorator_with_args(p1,p2):       # 1. 定义外层函数
        def actual_decorator(func):       # 2. 定义中层函数(真正的装饰器)
            def wrapper(*args,**kwargs):
                ...
            return wrapper
        return actual_decorator

运行时(装饰发生时):
    @decorator_with_args('hello',42)      # 3. 立即调用外层函数,返回 actual_decorator
    def say_hello(): ...                  # 4. 定义原函数

    # 上面两步等效于:
    # actual = decorator_with_args('hello',42)   # 步骤 A
    # say_hello = actual(say_hello)              # 步骤 B

实际调用时:
    say_hello()                           # 5. 调用 wrapper
        -> wrapper() 内:
            -> 可以使用 'hello',42
            -> 原 say_hello()

三、带参数装饰器的特殊情况:@decorator()(空括号)

python 复制代码
def decorator_with_default(func=None):
    def actual_decorator(func):
        def wrapper(*args,**kwargs):
            ...
        return wrapper
    if func is None:
        return actual_decorator
    else:
        return actual_decorator(func)

这种写法允许 @decorator()@decorator 两种用法,但本质仍然是三层结构,只是通过参数默认值做了兼容。不过这是高级技巧,笔记中可以作为延伸。


四、核心区别总结表(用于笔记)

维度 不带参数 @deco 带参数 @deco(arg)
嵌套层数 2 层(deco → wrapper) 3 层(deco → actual_deco → wrapper)
@ 后的表达式 函数对象 函数调用(立即执行)
外层函数何时执行 @ 解析时不调用,只是引用 @ 解析时立即调用,并传入参数
谁接收被装饰函数 deco 直接接收 actual_deco(外层函数的返回值)接收
闭包捕获 wrapper 只能捕获 func wrapper 可以捕获外层参数和 func
灵活性 行为固定 可配置(如重试次数、日志级别)
等价代码 say_hello = deco(say_hello) say_hello = deco(arg)(say_hello)

五、完整示例(带详细注释)

不带参数:简单计时器

python 复制代码
import time

def timer(func):                     # 第1层:接收被装饰函数
    def wrapper(*args, **kwargs):    # 第2层:包裹函数
        start = time.time()
        result = func(*args, **kwargs)
        print(f"耗时 {time.time()-start:.2f}s")
        return result
    return wrapper                    # 返回 wrapper,替换原函数

@timer
def sleep_one():
    time.sleep(1)

# 调用过程:
# 1. @timer → timer(sleep_one) → 返回 wrapper
# 2. sleep_one 现在等于 wrapper
# 3. sleep_one() → 执行 wrapper

带参数:可指定重复次数的装饰器

python 复制代码
def repeat(times):                   # 第1层:接收装饰器参数
    print(f"外层函数执行,times={times}")
    def decorator(func):             # 第2层:接收被装饰函数(真正的装饰器)
        print(f"中层函数执行,func={func.__name__}")
        def wrapper(*args, **kwargs):# 第3层:包裹函数
            print(f"wrapper 执行,准备重复 {times} 次")
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator                  # 返回中层函数

@repeat(times=3)                     # 1. 立即调用 repeat(3) → 返回 decorator
def greet(name):                     # 2. 此时 @decorator 生效 → decorator(greet) → 返回 wrapper
    print(f"Hello {name}")

# 调用:
greet("Alice")
# 输出:
# 外层函数执行,times=3
# 中层函数执行,func=greet
# wrapper 执行,准备重复 3 次
# Hello Alice
# Hello Alice
# Hello Alice

六、记忆口诀

  • 不带参数 :两层包,@ 后直接跟名字,原函数做参数,返回内层替原身。
  • 带参数 :三层套,@ 后先调外层拿中层,中层再收原函数,内层闭包用参数。
相关推荐
颜酱2 小时前
# 02 | 搭骨架:用 LangGraph 编排 12 步工作流(思路)
前端·人工智能·后端
颜酱2 小时前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑3 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康3 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅3 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries4 小时前
文件上传系统
后端
止语Lab5 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活5 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困5 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn5 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端