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 小时前
设计模式:构造器模式
开发语言·c++·后端·学习·设计模式
SimonKing3 小时前
大V说’AI替代不了你’,但现实是——用AI的人正在替代你
java·后端·程序员
IT_陈寒3 小时前
SpringBoot里的这个坑差点让我加班到天亮
前端·人工智能·后端
BingoGo3 小时前
Laravel13 + Vue3 的免费可商用 PHP 管理后台 CatchAdmin V5.2.0 发布
后端·php·laravel
rannn_1114 小时前
【Redis|高级篇1】分布式缓存|持久化(RDB、AOF)、主从集群、哨兵、分片集群
java·redis·分布式·后端·缓存
weixin_408099674 小时前
【实战教程】EasyClick 调用 OCR 文字识别 API(自动识别屏幕文字 + 完整示例代码)
前端·人工智能·后端·ocr·api·安卓·easyclick
添尹4 小时前
Go语言基础之指针
开发语言·后端·golang
GreenTea13 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
我是大猴子15 小时前
Spring代理类为何依赖注入失效?
java·后端·spring