Python进阶-闭包与装饰器


1. 课前必学:闭包(Closure)

装饰器的本质是闭包,所以先搞懂闭包是基础!

1.1 什么是闭包

简单来说:

  • 有一个外层函数 ,里面定义了一个内层函数
  • 内层函数使用了外层函数的变量
  • 外层函数返回了内层函数对象(不加括号)

1.2 闭包完整代码示例与逻辑拆解

复制代码
def outer(x):
    # 外层函数定义了变量 x,它会被内层函数"捕获"并保存
    
    def inner(y):
        # 内层函数使用了外层的 x(这是闭包的关键)
        return x + y
    
    # 外层函数返回内层函数对象(注意:不加括号!)
    # 这里返回的不是函数执行结果,而是函数本身
    return inner

# 第一步:调用外层函数,传入 x=10
# 此时 outer 执行完毕,但它的变量 x=10 会被闭包"保存下来"
f = outer(10)

# 第二步:调用内层函数,传入 y=5
# 此时 inner 会使用之前保存的 x=10,计算 10+5
print(f(5))  # 输出:15

# 再试一次:传入 y=20
print(f(20)) # 输出:30(x 依然是保存的 10)

逻辑拆解

  1. 执行 outer(10) 时,外层函数的变量 x 被赋值为 10
  2. 外层函数执行完毕,返回内层函数 inner(不是调用它,所以不加括号)
  3. 把返回的 inner 赋值给变量 f
  4. 调用 f(5) 时,实际执行的是 inner(5),它用了之前保存的 x=10,所以返回 10+5=15

1.3 闭包的3个核心条件

  1. ✅ 有外层函数和内层函数
  2. ✅ 内层函数使用了外层函数的变量
  3. ✅ 外层函数返回内层函数对象(不加括号)

1.4 闭包和普通嵌套函数的区别

先记住一句核心结论
  • 函数嵌套 :只是写法上,函数里套了个函数 → 单纯的「代码结构」
  • 闭包 :是满足3个条件的特殊函数嵌套 → 带记忆功能的「运行状态」

不是所有嵌套都是闭包,但闭包一定是嵌套。

(1)先看:什么是「纯函数嵌套」?

定义 :在一个函数内部定义 另一个函数,仅此而已。
特点

  1. 只是语法上套一层
  2. 内层函数一般就在外层里直接调用
  3. 外层函数执行完,内层函数和外层变量全部销毁
  4. 没有记忆功能
纯嵌套代码(不是闭包)
复制代码
def outer():  # 外层函数
    x = 10    # 外层变量
    
    def inner():  # 内层函数 → 这就是「函数嵌套」
        print("我是内层函数")  # 注意:我没用到外层的 x!
    
    inner()  # 在外层里直接调用内层
    # 外层函数执行完,x 和 inner 全都没了

# 调用
outer()  # 输出:我是内层函数

👉 这就是普通函数嵌套 :套着写、用完就扔、不留任何东西

(2)再看:什么是「闭包」?

闭包 = 函数嵌套 + 3个必须条件,缺一不可:

  1. 有函数嵌套(外层里定义内层)
  2. 内层函数使用了外层函数的变量/参数
  3. 外层函数返回内层函数本身(不直接调用)

核心能力

外层函数执行完已经结束了 ,内层函数依然能记住并使用 外层的变量 → 带记忆!

闭包代码(是特殊的嵌套)
复制代码
def outer():
    x = 10  # 外层变量
    
    def inner():
        # 条件2:内层使用了外层的变量 x
        print(x)  
    
    # 条件3:外层返回内层函数本身(不加括号!)
    return inner  

# 1. 执行 outer,outer 执行完毕,x 本该被销毁
f = outer()  

# 2. 但!调用 f(就是 inner),依然能拿到 x=10
f()  # 输出:10 → 这就是闭包!

👉 关键点:
outer 早就跑完了,但 inner 依然记住了 x,这就是闭包的灵魂。

(3)多维表格对比:区别到底在哪?

|-----------|---------------|-------------------|
| 特性 | 普通函数嵌套 | 闭包 |
| 本质 | 只是代码写法/结构 | 是带记忆的函数状态 |
| 内层是否用外层变量 | 可不用,随便 | 必须用 |
| 外层是否返回内层 | 一般不返回,直接调用 | 必须返回内层函数 |
| 变量生命周期 | 外层结束,变量全销毁 | 外层结束,被引用的变量保留 |
| 功能 | 单纯拆分代码,方便阅读 | 记住状态,数据持久化 |

(4)超通俗比喻(一下就懂)
1. 普通函数嵌套

= 你在大箱子里放了个小盒子

打开大箱子,拿出小盒子用一次,然后大箱子、小盒子全扔了 ,里面的东西也没了。

→ 只是「套着放」,没别的用。

2. 闭包

= 大箱子里有个小盒子,你拿出了小盒子,小盒子装了大箱子里的一个东西,

然后你把大箱子关上、锁死 (外层函数执行完)

但小盒子依然拿着那个东西 ,随时能用。

小盒子记住了大箱子里的东西,这就是闭包。

(5)再给一个「能看懂用途」的闭包例子

比如:做一个计数器,记住自己加了多少次

复制代码
# 闭包:生成计数器
def make_counter():
    count = 0  # 外层变量,被记住
    
    def counter():
        nonlocal count
        count += 1
        return count
    
    return counter

# 创建两个计数器,互相不影响
c1 = make_counter()
c2 = make_counter()

print(c1())  # 1
print(c1())  # 2
print(c2())  # 1

👉 普通嵌套做不到记住 count,只有闭包可以。


2. 装饰器入门:基础概念与无参装饰器

2.1 装饰器的本质

装饰器就是一个特殊的闭包

  • 外层函数的参数是一个函数(被装饰的原函数)
  • 内层函数包裹原函数,在前后添加功能
  • 外层函数返回内层函数对象

装饰器的作用:在不修改原函数代码的前提下,增强原函数的功能


2.2 场景1:无参无返 + 只加动作

需求:给一个没有参数、没有返回值的函数,添加"执行前打印提示"的功能。

复制代码
# ---------------------- 1. 定义装饰器 ----------------------
def my_decorator(fn):
    # fn 是被装饰的原函数(比如下面的 say_hello)
    
    def inner():
        # 【加动作】在原函数执行前,打印提示
        print("=== 函数开始执行啦!===")
        # 【调用原函数】执行原函数的核心逻辑
        fn()
        # 【加动作】在原函数执行后,也可以加动作(这里暂时不加)
    
    # 装饰器必须返回内层函数对象(不加括号!)
    return inner

# ---------------------- 2. 使用装饰器 ----------------------
# @my_decorator 是语法糖,等价于:say_hello = my_decorator(say_hello)
@my_decorator
def say_hello():
    # 原函数:无参无返,只负责打印"你好"
    print("你好!")

# ---------------------- 3. 调用被装饰后的函数 ----------------------
say_hello()

输出

复制代码
=== 函数开始执行啦!===
你好!

逻辑拆解

  • @my_decorator 把原 say_hello 函数传给 my_decorator,得到内层函数 inner
  • 调用 say_hello() 时,实际执行的是 inner()
  • inner() 先打印提示,再调用原 say_hello()

2.3 场景2:无参有返 + 只加动作

需求 :给一个没有参数、但有返回值的函数,添加"执行前打印"的功能,不修改原函数的返回值

复制代码
# ---------------------- 1. 定义装饰器 ----------------------
def my_decorator(fn):
    def inner():
        print("=== 函数开始执行啦!===")
        # 【调用原函数并保存结果】原函数有返回值,所以要存下来
        original_result = fn()
        # 【返回原结果】我们的需求只是"加动作",不改结果,所以直接返回原结果
        return original_result
    return inner

# ---------------------- 2. 使用装饰器 ----------------------
@my_decorator
def get_num():
    # 原函数:无参,但有返回值 100
    return 100

# ---------------------- 3. 调用被装饰后的函数 ----------------------
result = get_num()
print(f"函数返回值是:{result}")

输出

复制代码
=== 函数开始执行啦!===
函数返回值是:100

关键点

  • 这里 return original_result 就是返回原函数的结果,对应"只加动作,不改结果"

2.4 场景3:无参有返 + 修改结果

需求 :给一个返回字符串的函数,添加 .txt 后缀,必须修改原函数的返回值

复制代码
# ---------------------- 1. 定义装饰器 ----------------------
def str_decorator(fn):
    def inner():
        # 【第一步】调用原函数,拿到它返回的原始字符串
        original_str = fn()
        # 【第二步】加工:给原始字符串添加 .txt 后缀
        new_str = original_str + ".txt"
        # 【第三步】返回加工后的新结果(不是原结果!)
        return new_str
    return inner

# ---------------------- 2. 使用装饰器 ----------------------
@str_decorator
def str_return():
    # 原函数:返回字符串 "你好"
    return "你好"

# ---------------------- 3. 调用被装饰后的函数 ----------------------
result = str_return()
print(f"装饰后的结果是:{result}")

输出

复制代码
装饰后的结果是:你好.txt

逻辑拆解

  • 如果你写成 return fn(),就等于没加工,装饰器白写了!
  • 这里必须 return new_str,对应"要修改结果"

2.5 🔥 核心决策:新手如何选择 return 写法(重点)

这是新手学装饰器最卡壳的地方,我用**"决策三步法"**+ 完整对比示例帮你彻底搞懂!

第一步:先问自己两个问题

拿到装饰器需求时,先在心里问:

  1. 我需要修改原函数的返回值吗?
    • 不需要 → 走"只加动作"路线
    • 需要 → 走"修改结果"路线
  1. 原函数有返回值吗?
    • 没有 → 直接调用 fn() 就行,不用 return
    • 有 → 根据问题1决定 return 什么
第二步:根据答案选写法

|-----------|--------|-------------------------------|----------|
| 需求类型 | 原函数返回值 | 内层函数写法 | 完整代码示例 |
| 只加动作,不改结果 | 无返回值 | fn()(不用return) | 见下方【示例A】 |
| 只加动作,不改结果 | 有返回值 | res = fn() return res | 见上方【场景2】 |
| 要修改结果 | 有返回值 | res = 加工(fn()) return res | 见上方【场景3】 |

第三步:完整对比示例
【示例A】无参无返 + 只加动作
复制代码
# ✅ 正确写法
def decorator(fn):
    def inner():
        print("执行前")
        fn()  # 原函数没返回值,直接调用就行
    return inner

@decorator
def say_hi():
    print("Hi!")

say_hi()
# 输出:
# 执行前
# Hi!

【示例B】无参有返 + 只加动作(错误 vs 正确)
复制代码
# ❌ 错误写法:忘了保存原结果,直接return fn()也可以,但如果要在中间加动作,最好先保存
def decorator_wrong(fn):
    def inner():
        print("执行前")
        return fn()  # 其实这样也能跑,但如果要在fn()后加动作,就必须先保存
    return inner

# ✅ 正确写法:先保存原结果,再加动作,最后返回
def decorator_right(fn):
    def inner():
        print("执行前")
        res = fn()  # 先保存原结果
        print("执行后")  # 可以在fn()后加动作
        return res  # 最后返回原结果
    return inner

@decorator_right
def get_100():
    return 100

print(get_100())
# 输出:
# 执行前
# 执行后
# 100

【示例C】无参有返 + 修改结果(错误 vs 正确)
复制代码
# ❌ 错误写法:加工了,但没返回加工后的结果,反而返回了原结果
def decorator_wrong(fn):
    def inner():
        fn() + ".txt"  # 加工了,但没存下来
        return fn()     # 返回了原结果,白加工了
    return inner

@decorator_wrong
def str_return_wrong():
    return "你好"

print(str_return_wrong())  # 输出:你好(没加后缀)

# --------------------------------------------------

# ✅ 正确写法:保存加工后的结果,并返回它
def decorator_right(fn):
    def inner():
        original = fn()       # 1. 拿原结果
        new_result = original + ".txt"  # 2. 加工
        return new_result     # 3. 返回新结果
    return inner

@decorator_right
def str_return_right():
    return "你好"

print(str_return_right())  # 输出:你好.txt

3. 装饰器进阶:处理有参函数与可变参数

3.1 问题:原函数有参数怎么办?

如果原函数有参数,但装饰器的内层函数不接收参数,就会报错!

错误示例

复制代码
def my_decorator(fn):
    def inner():  # ❌ 内层函数不接收参数
        print("=== 开始执行 ===")
        fn()  # ❌ 原函数需要参数,但这里没传
    return inner

@my_decorator
def add(a, b):  # 原函数需要两个参数
    return a + b

add(1, 2)  # 报错:inner() takes 0 positional arguments but 2 were given

3.2 解决方案:*args**kwargs

*args 接收任意位置参数 ,用 **kwargs 接收任意关键字参数,让装饰器通用!

  • *args:把传入的位置参数打包成一个元组 (比如 (1, 2)
  • **kwargs:把传入的关键字参数打包成一个字典 (比如 {'name': '小明'}

3.3 场景4:有参有返 + 加动作 + 通用参数(完整代码)

复制代码
# ---------------------- 1. 定义通用装饰器 ----------------------
def my_decorator(fn):
    # *args:接收任意位置参数
    # **kwargs:接收任意关键字参数
    def inner(*args, **kwargs):
        print(f"=== 收到参数:args={args}, kwargs={kwargs} ===")
        # 把参数原封不动传给原函数
        result = fn(*args, **kwargs)
        return result
    return inner

# ---------------------- 2. 测试1:两个位置参数 ----------------------
@my_decorator
def add(a, b):
    return a + b

# ---------------------- 3. 测试2:一个位置参数 + 一个关键字参数 ----------------------
@my_decorator
def greet(name, greeting="你好"):
    return f"{greeting},{name}!"

# ---------------------- 4. 调用测试 ----------------------
print(add(1, 2))
print(greet("小明", greeting="早上好"))

输出

复制代码
=== 收到参数:args=(1, 2), kwargs={} ===
3
=== 收到参数:args=('小明',), kwargs={'greeting': '早上好'} ===
早上好,小明!

4. 装饰器高级:nonlocal关键字与状态保持

4.1 问题:装饰器需要计数/记录状态怎么办?

如果想让装饰器记录"函数被调用了多少次",就需要修改外层函数的变量,这时要用到 nonlocal 关键字。


4.2 nonlocal关键字的作用

  • 告诉Python:"这个变量不是局部变量,去外层函数找"
  • 区别于 globalglobal 是找全局变量nonlocal 是找外层函数变量

4.3 场景5:带计数功能的装饰器(完整代码)

复制代码
# ---------------------- 1. 定义带计数的装饰器 ----------------------
def count_decorator(fn):
    # 外层变量:记录函数被调用的次数
    count = 0
    
    def inner(*args, **kwargs):
        # 声明:count 是外层函数的变量,我要修改它
        nonlocal count
        count += 1
        print(f"这是第 {count} 次调用 {fn.__name__} 函数")
        # 调用原函数
        return fn(*args, **kwargs)
    
    return inner

# ---------------------- 2. 使用装饰器 ----------------------
@count_decorator
def say_hello():
    print("你好!")

# ---------------------- 3. 调用测试 ----------------------
say_hello()
say_hello()
say_hello()

输出

复制代码
这是第 1 次调用 say_hello 函数
你好!
这是第 2 次调用 say_hello 函数
你好!
这是第 3 次调用 say_hello 函数
你好!

关键点

  • 如果不用 nonlocal,会报错 UnboundLocalError(Python认为 count 是局部变量,但没定义)
  • 用了 nonlocal,Python就会去外层函数找 count 并修改它

5. 装饰器复用与叠加

5.1 一个装饰器装饰多个函数(完整代码)

装饰器的一大优势就是复用:同一个装饰器可以给多个函数加功能!

复制代码
import time

# ---------------------- 1. 定义通用的"计时装饰器" ----------------------
def timer_decorator(fn):
    def inner(*args, **kwargs):
        start_time = time.time()  # 记录开始时间
        result = fn(*args, **kwargs)
        end_time = time.time()    # 记录结束时间
        print(f"{fn.__name__} 函数执行耗时:{end_time - start_time:.4f}秒")
        return result
    return inner

# ---------------------- 2. 给多个函数用同一个装饰器 ----------------------
@timer_decorator
def add(a, b):
    time.sleep(0.1)  # 模拟耗时操作
    return a + b

@timer_decorator
def say_hello(name):
    time.sleep(0.2)  # 模拟耗时操作
    return f"你好,{name}!"

# ---------------------- 3. 调用测试 ----------------------
print(add(1, 2))
print(say_hello(name="小明"))

输出

复制代码
add 函数执行耗时:0.1001秒
3
say_hello 函数执行耗时:0.2002秒
你好,小明!

5.2 多个装饰器装饰一个函数(完整代码+执行顺序)

可以给一个函数加多个装饰器,执行顺序是从下到上(离函数近的先执行)!

复制代码
# ---------------------- 1. 定义两个装饰器 ----------------------
def decorator1(fn):
    def inner():
        print("装饰器1:执行前")
        fn()
        print("装饰器1:执行后")
    return inner

def decorator2(fn):
    def inner():
        print("装饰器2:执行前")
        fn()
        print("装饰器2:执行后")
    return inner

# ---------------------- 2. 叠加装饰器 ----------------------
# 离函数近的 decorator2 先执行包装,然后是 decorator1执行包装
@decorator1
@decorator2
def say_hello():
    print("你好!")

# ---------------------- 3. 调用测试 ----------------------
say_hello()

输出

复制代码
装饰器1:执行前
装饰器2:执行前
你好!
装饰器2:执行后
装饰器1:执行后

逻辑拆解

  • @decorator1 @decorator2 等价于 say_hello = decorator1(decorator2(say_hello))
  • 执行顺序:
    1. 先执行 decorator1inner()(打印"装饰器1:执行前")
    2. 再执行 decorator2inner()(打印"装饰器2:执行前")
    3. 然后执行原 say_hello()(打印"你好!")
    4. 最后返回:先打印"装饰器2:执行后",再打印"装饰器1:执行后"

6.装饰器带有参数


6.1 先搞懂核心背景:为什么需要「带参数的装饰器」?

我们之前学的普通无参装饰器 是2层嵌套,只能固定逻辑,无法根据参数动态改变行为。

但现在的需求是:同一个装饰器,装饰加法时打印「加法日志」,装饰减法时打印「减法日志」

带参装饰器的本质

带参装饰器 = 「一个能接收参数、并返回普通装饰器的工厂函数」

结构从「2层嵌套」升级为「3层嵌套」,每一层有明确职责:

|----------|---------------------------|--------|
| 嵌套层级 | 核心职责 | 接收的内容 |
| 最外层(第1层) | 接收装饰器的自定义参数(如'+'/'-') | 装饰器的参数 |
| 中间层(第2层) | 接收被装饰的原函数 | 原函数对象 |
| 最内层(第3层) | 包装原函数、加额外功能、执行原函数 | 原函数的参数 |


6.2 逐行拆解代码(从装饰器到原函数)

📌 todo 1:定义带参数的装饰器 logging
复制代码
# 最外层:第1层,接收装饰器的参数 flag
def logging(flag):
    # 中间层:第2层,接收被装饰的原函数(原代码命名fn_name,实际是函数对象,推荐改名为fn)
    def my_decorator(fn_name):
        # 最内层:第3层,接收原函数的参数 a、b
        def fun_inner(a, b):
            # 👇 额外功能:根据flag打印不同日志
            if flag == '+':
                print('正在努力计算加法中...')
            elif flag == '-':
                print('正在努力计算减法中...')
            # 👇 调用原函数,传入参数,原样返回结果(保证原函数功能不变)
            return fn_name(a, b)
        # 👇 中间层返回最内层的包装函数(普通装饰器的标准返回)
        return fun_inner
    # 👇 最外层返回中间层的「普通装饰器」
    return my_decorator
1. 最外层:def logging(flag):
  • 作用 :专门接收装饰器的参数,也就是写@logging('+')时的'+',或@logging('-')时的'-'
  • 本质 :「装饰器工厂」------根据传入的flag,生成一个记住了flag的专属装饰器
  • 闭包关联 :这里的flag会被内层fun_inner引用,因此即使logging执行完,fun_inner依然能永久记住flag的值(这就是你之前学的闭包的记忆功能!)
2. 中间层:def my_decorator(fn_name):
  • 作用 :标准的「普通装饰器外层」,接收被装饰的原函数
    • 装饰get_sum时:fn_name = get_sum(加法函数对象)
    • 装饰get_sub时:fn_name = get_sub(减法函数对象)
  • 注意 :原代码fn_name命名容易混淆(看起来像函数名,实际是函数对象),更推荐命名为fn,更直观。
3. 最内层:def fun_inner(a, b):
  • 作用:最终替换原函数的「包装函数」,是装饰器的核心执行层。
    • 接收原函数的参数:get_sumget_sub的参数都是a,b,因此这里写a,b
    • 加额外功能:if-elif判断flag,打印对应日志。
    • 执行原函数:return fn_name(a, b) 调用原函数、传参,并原样返回原函数的结果(保证装饰后原函数功能完全不变)。
4. 三层返回的强制逻辑(缺一不可!)
  • 最内层fun_inner → 被中间层my_decorator返回(普通装饰器的标准:外层返回内层包装函数)
  • 中间层my_decorator → 被最外层logging返回(带参装饰器的核心:最外层返回「普通装饰器」)

📌 todo 2:定义加法原函数 get_sum
复制代码
# 装饰器语法糖:@logging('+') 会先执行 logging('+'),再用返回值装饰get_sum
@logging('+')
def get_sum(a, b):
    return a + b
语法糖@logging('+')的执行逻辑(小白最容易懵的重点!)
  1. 第一步:执行 logging('+')
    传入flag='+',执行完后logging返回my_decorator(这个my_decorator已经通过闭包记住了flag='+'
  2. 第二步:用 my_decorator装饰 get_sum
    相当于执行get_sum = my_decorator(get_sum),最终get_sum这个名字被替换成fun_inner
  3. 本质 :原函数被「包装函数」替换,调用get_sum时实际执行fun_inner

📌 todo 3:定义减法原函数 get_sub
复制代码
@logging('-')
def get_sub(a, b):
    return a - b

逻辑和加法完全一致:

  1. 执行logging('-'),返回记住flag='-'my_decorator
  2. my_decorator装饰get_subget_sub被替换成fun_inner
  3. 原函数get_sub的减法功能完全保留,仅新增打印日志的功能

6.3 完整执行流程拆解(从装饰到调用,一步一步走)

以调用get_sum(3,5)为例,我们把每一步都拆透:

🔹 步骤1:装饰阶段(代码加载时执行,不是调用时!)
  1. 执行@logging('+')
    调用logging('+')flag='+',内部定义my_decorator,返回my_decorator(已闭包引用flag='+'
  2. my_decorator装饰get_sum
    调用my_decorator(get_sum)fn_name=get_sum,内部定义fun_inner(引用flag='+'fn_name=get_sum
    my_decorator返回fun_inner,最终get_sum = fun_inner(原函数被替换)
🔹 步骤2:调用阶段(执行get_sum(3,5)时)
  1. 因为get_sum已被替换为fun_inner,实际执行fun_inner(3,5)
  2. 进入fun_inner
    • 判断flag='+',打印正在努力计算加法中...
    • 执行fn_name(3,5) → 原get_sum(3,5),得到结果8
    • 返回结果8
  1. 最终输出:

    正在努力计算加法中...
    8

🔹 同理,调用get_sub(10,4)的流程
  1. 装饰阶段:logging('-')返回记住flag='-'my_decoratorget_sub被替换为fun_inner

  2. 调用get_sub(10,4) → 执行fun_inner(10,4)

  3. 判断flag='-',打印正在努力计算减法中...

  4. 执行get_sub(10,4),返回6

  5. 最终输出:

    正在努力计算减法中...
    6


6.4 结合闭包知识,再讲透「为什么能记住flag」

这里的三层嵌套就是闭包的典型应用

闭包的3个必要条件(全部满足)
  1. 函数嵌套loggingmy_decoratorfun_inner,三层嵌套
  2. 内层引用外层变量fun_inner引用了loggingflag,和my_decoratorfn_name
  3. 外层返回内层函数logging返回my_decoratormy_decorator返回fun_inner
闭包的核心作用

loggingmy_decorator执行完后,它们的局部变量flagfn_name本来应该被销毁,但因为被fun_inner引用,所以被永久保留下来:

  • 装饰get_sumfun_inner:记住flag='+'fn_name=get_sum
  • 装饰get_subfun_inner:记住flag='-'fn_name=get_sub
  • 两个fun_inner完全独立,互不干扰(就像你之前学的计数器闭包,两个计数器互不影响)

6.5 代码优化建议(符合Python最佳实践)

原代码有几个可以优化的点,适合小白学习规范写法:

1. 优化命名,避免混淆

把容易混淆的fn_name改为fn,更直观:

复制代码
def logging(flag):
    def my_decorator(fn):  # 改为fn,明确是函数对象
        def fun_inner(a, b):
            if flag == '+':
                print('正在努力计算加法中...')
            elif flag == '-':
                print('正在努力计算减法中...')
            return fn(a, b)
        return fun_inner
    return my_decorator
2. 通用化包装函数,支持任意参数

原代码fun_inner写死了a,b,只能装饰2个参数的函数,改为*args, **kwargs可装饰任意函数:

复制代码
def logging(flag):
    def my_decorator(fn):
        def fun_inner(*args, **kwargs):  # 支持任意位置/关键字参数
            if flag == '+':
                print('正在努力计算加法中...')
            elif flag == '-':
                print('正在努力计算减法中...')
            return fn(*args, **kwargs)  # 原样传参
        return fun_inner
    return my_decorator
3. 保留原函数元信息(functools.wraps

装饰器会替换原函数的元信息(如函数名、文档字符串),用wraps可保留原信息:

复制代码
from functools import wraps  # 导入工具

def logging(flag):
    def my_decorator(fn):
        @wraps(fn)  # 复制原函数的元信息到包装函数
        def fun_inner(*args, **kwargs):
            if flag == '+':
                print('正在努力计算加法中...')
            elif flag == '-':
                print('正在努力计算减法中...')
            return fn(*args, **kwargs)
        return fun_inner
    return my_decorator

6.6 完整测试代码+运行结果

复制代码
from functools import wraps

# 带参数的装饰器(优化版)
def logging(flag):
    def my_decorator(fn):
        @wraps(fn)
        def fun_inner(*args, **kwargs):
            if flag == '+':
                print('正在努力计算加法中...')
            elif flag == '-':
                print('正在努力计算减法中...')
            return fn(*args, **kwargs)
        return fun_inner
    return my_decorator

# 加法函数
@logging('+')
def get_sum(a, b):
    return a + b

# 减法函数
@logging('-')
def get_sub(a, b):
    return a - b

# 测试
if __name__ == '__main__':
    print(get_sum(3, 5))   # 加法测试
    print(get_sub(10, 4)) # 减法测试
运行结果:
复制代码
正在努力计算加法中...
8
正在努力计算减法中...
6

6.7 核心总结(帮你快速记忆)

  1. 带参装饰器结构:3层嵌套,外层收参数、中层收原函数、内层做包装
  2. 语法糖执行顺序@logging('+') → 先执行logging('+')拿装饰器,再装饰原函数
  3. 闭包的作用:让包装函数永久记住外层参数,即使外层函数执行完也不销毁
  4. 装饰器核心原则:不修改原函数代码,仅新增功能(开闭原则)

6.8 小白常见误区解答

误区1:为什么不能用2层嵌套?

如果用2层,@logging('+')会把'+'当成原函数传入,直接报错。2层嵌套的外层只能接收原函数,无法接收自定义参数,因此必须用3层。

误区2:为什么要返回原函数的结果?

如果不return fn(a,b),原函数的结果会丢失,装饰后的函数就没有返回值,破坏了原函数的功能。装饰器必须保证「原函数行为完全不变」,因此必须原样返回结果。

误区3:两个装饰后的函数会互相干扰吗?

不会!每次调用logging('+')/logging('-')都会生成全新的my_decoratorfun_inner,每个fun_inner都有独立的闭包环境,完全隔离。


7. 总结

  1. 闭包:外层函数返回内层函数对象,内层函数使用外层变量
  2. 装饰器本质:特殊的闭包,参数是函数,用来增强原函数功能
  3. 🔥 return决策三步法
    • 先问:要不要改原函数返回值?
    • 再问:原函数有没有返回值?
    • 最后选写法:只加动作→返回原结果;改结果→返回新结果
  1. 通用参数*args**kwargs 让装饰器适配任意参数
  2. nonlocal:修改外层函数变量时用
  3. 装饰器叠加:从下到上执行
相关推荐
你的保护色2 小时前
华为eNSP网络实验之IPsec协议学习
网络·学习·华为
forEverPlume2 小时前
CSS如何实现背景颜色的棋盘格分布_利用repeating-gradient
jvm·数据库·python
qq_424098562 小时前
CSS如何实现CSS按路径引入_利用动态路由加载对应样式模块
jvm·数据库·python
知识分享小能手2 小时前
ECharts入门学习教程,从入门到精通,综合实战——ECharts数据大屏 - 完整知识点(9)
前端·学习·echarts
m0_684501982 小时前
SQL窗口函数与数据透视表对比_适用场景分析
jvm·数据库·python
凯瑟琳.奥古斯特2 小时前
C++变量命名进阶技巧
开发语言·c++
m0_748920362 小时前
mysql连接无法释放导致执行中断_配置wait_timeout与连接池优化
jvm·数据库·python
不羁的fang少年2 小时前
Netty网络模型
java·开发语言
m0_684501982 小时前
PHP函数如何适配异构计算硬件平台_PHP在CPU+GPU+FPGA运行【方法】
jvm·数据库·python