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)
逻辑拆解:
- 执行
outer(10)时,外层函数的变量x被赋值为10 - 外层函数执行完毕,返回内层函数
inner(不是调用它,所以不加括号) - 把返回的
inner赋值给变量f - 调用
f(5)时,实际执行的是inner(5),它用了之前保存的x=10,所以返回10+5=15
1.3 闭包的3个核心条件
- ✅ 有外层函数和内层函数
- ✅ 内层函数使用了外层函数的变量
- ✅ 外层函数返回内层函数对象(不加括号)
1.4 闭包和普通嵌套函数的区别
先记住一句核心结论
- 函数嵌套 :只是写法上,函数里套了个函数 → 单纯的「代码结构」
- 闭包 :是满足3个条件的特殊函数嵌套 → 带记忆功能的「运行状态」
不是所有嵌套都是闭包,但闭包一定是嵌套。
(1)先看:什么是「纯函数嵌套」?
定义 :在一个函数内部定义 另一个函数,仅此而已。
特点:
- 只是语法上套一层
- 内层函数一般就在外层里直接调用
- 外层函数执行完,内层函数和外层变量全部销毁
- 没有记忆功能
纯嵌套代码(不是闭包)
def outer(): # 外层函数
x = 10 # 外层变量
def inner(): # 内层函数 → 这就是「函数嵌套」
print("我是内层函数") # 注意:我没用到外层的 x!
inner() # 在外层里直接调用内层
# 外层函数执行完,x 和 inner 全都没了
# 调用
outer() # 输出:我是内层函数
👉 这就是普通函数嵌套 :套着写、用完就扔、不留任何东西。
(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 写法(重点)
这是新手学装饰器最卡壳的地方,我用**"决策三步法"**+ 完整对比示例帮你彻底搞懂!
第一步:先问自己两个问题
拿到装饰器需求时,先在心里问:
- 我需要修改原函数的返回值吗?
-
- 不需要 → 走"只加动作"路线
- 需要 → 走"修改结果"路线
- 原函数有返回值吗?
-
- 没有 → 直接调用
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:"这个变量不是局部变量,去外层函数找"
- 区别于
global:global是找全局变量 ,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))- 执行顺序:
-
- 先执行
decorator1的inner()(打印"装饰器1:执行前") - 再执行
decorator2的inner()(打印"装饰器2:执行前") - 然后执行原
say_hello()(打印"你好!") - 最后返回:先打印"装饰器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_sum和get_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('+')的执行逻辑(小白最容易懵的重点!)
- 第一步:执行
logging('+')
传入flag='+',执行完后logging返回my_decorator(这个my_decorator已经通过闭包记住了flag='+') - 第二步:用
my_decorator装饰get_sum
相当于执行get_sum = my_decorator(get_sum),最终get_sum这个名字被替换成fun_inner - 本质 :原函数被「包装函数」替换,调用
get_sum时实际执行fun_inner
📌 todo 3:定义减法原函数 get_sub
@logging('-')
def get_sub(a, b):
return a - b
逻辑和加法完全一致:
- 执行
logging('-'),返回记住flag='-'的my_decorator - 用
my_decorator装饰get_sub,get_sub被替换成fun_inner - 原函数
get_sub的减法功能完全保留,仅新增打印日志的功能
6.3 完整执行流程拆解(从装饰到调用,一步一步走)
以调用get_sum(3,5)为例,我们把每一步都拆透:
🔹 步骤1:装饰阶段(代码加载时执行,不是调用时!)
- 执行
@logging('+'):
调用logging('+'),flag='+',内部定义my_decorator,返回my_decorator(已闭包引用flag='+') - 用
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)时)
- 因为
get_sum已被替换为fun_inner,实际执行fun_inner(3,5) - 进入
fun_inner:
-
- 判断
flag='+',打印正在努力计算加法中... - 执行
fn_name(3,5)→ 原get_sum(3,5),得到结果8 - 返回结果
8
- 判断
-
最终输出:
正在努力计算加法中...
8
🔹 同理,调用get_sub(10,4)的流程
-
装饰阶段:
logging('-')返回记住flag='-'的my_decorator,get_sub被替换为fun_inner -
调用
get_sub(10,4)→ 执行fun_inner(10,4) -
判断
flag='-',打印正在努力计算减法中... -
执行
get_sub(10,4),返回6 -
最终输出:
正在努力计算减法中...
6
6.4 结合闭包知识,再讲透「为什么能记住flag」
这里的三层嵌套就是闭包的典型应用:
闭包的3个必要条件(全部满足)
- 函数嵌套 :
logging套my_decorator套fun_inner,三层嵌套 - 内层引用外层变量 :
fun_inner引用了logging的flag,和my_decorator的fn_name - 外层返回内层函数 :
logging返回my_decorator,my_decorator返回fun_inner
闭包的核心作用
当logging和my_decorator执行完后,它们的局部变量flag和fn_name本来应该被销毁,但因为被fun_inner引用,所以被永久保留下来:
- 装饰
get_sum的fun_inner:记住flag='+'和fn_name=get_sum - 装饰
get_sub的fun_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 核心总结(帮你快速记忆)
- 带参装饰器结构:3层嵌套,外层收参数、中层收原函数、内层做包装
- 语法糖执行顺序 :
@logging('+')→ 先执行logging('+')拿装饰器,再装饰原函数 - 闭包的作用:让包装函数永久记住外层参数,即使外层函数执行完也不销毁
- 装饰器核心原则:不修改原函数代码,仅新增功能(开闭原则)
6.8 小白常见误区解答
误区1:为什么不能用2层嵌套?
如果用2层,@logging('+')会把'+'当成原函数传入,直接报错。2层嵌套的外层只能接收原函数,无法接收自定义参数,因此必须用3层。
误区2:为什么要返回原函数的结果?
如果不return fn(a,b),原函数的结果会丢失,装饰后的函数就没有返回值,破坏了原函数的功能。装饰器必须保证「原函数行为完全不变」,因此必须原样返回结果。
误区3:两个装饰后的函数会互相干扰吗?
不会!每次调用logging('+')/logging('-')都会生成全新的my_decorator和fun_inner,每个fun_inner都有独立的闭包环境,完全隔离。
7. 总结
- 闭包:外层函数返回内层函数对象,内层函数使用外层变量
- 装饰器本质:特殊的闭包,参数是函数,用来增强原函数功能
- 🔥
return决策三步法:
-
- 先问:要不要改原函数返回值?
- 再问:原函数有没有返回值?
- 最后选写法:只加动作→返回原结果;改结果→返回新结果
- 通用参数 :
*args和**kwargs让装饰器适配任意参数 nonlocal:修改外层函数变量时用- 装饰器叠加:从下到上执行