一、开篇引入:为什么要学闭包和装饰器?
想象一下,你有一台咖啡机,每次做咖啡前都需要先加热,做完后还要清洗。如果这些重复的“加热”和“清洗”步骤能自动完成,你只需要专注“放豆子”和“按开关”就好了。在编程里,闭包和装饰器就是帮你自动处理这些重复“套路”的工具。学会它们,你就能看懂很多高级代码,也能写出更优雅、更简洁的程序。这篇教程专为新手设计,用最通俗的比喻和最简单的代码,带你 30 分钟搞定闭包和装饰器。
二、闭包:函数带着“行李箱”
2.1 一句话定义闭包
闭包就是一个函数,它能记住并访问自己定义时所在的外部变量,即使外部函数已经执行完了。
2.2 最简单的闭包例子
先看一个最精简的闭包:
def outer(x): # 外部函数
def inner(y): # 内部函数
return x + y # inner 使用了 outer 的变量 x
return inner # 返回内部函数,注意不带括号
add_10 = outer(10) # 调用 outer,返回 inner 函数,此时 x=10
print(add_10(5)) # 输出 15,相当于 10 + 5
这里 inner 就是一个闭包,它“带着”外部变量 x=10 到处跑,哪怕 outer 早就执行完了。
2.3 闭包能做什么?
场景一:计数器
def make_counter():
count = 0 # 外层变量
def counter():
nonlocal count # 声明 count 是外层变量
count += 1
return count
return counter
cnt = make_counter() # cnt 就是一个闭包
print(cnt()) # 1
print(cnt()) # 2
print(cnt()) # 3
每次调用 cnt(),它都能“记住”上一次的 count,实现连续计数。
场景二:函数工厂
def make_multiplier(n):
def multiplier(x):
return x * n # 记住 n
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
闭包就像一个“函数工厂”,可以根据参数生成不同功能的函数。
三、装饰器:给函数穿“马甲”
3.1 一句话定义装饰器
装饰器就是一个接受函数作为参数、并返回一个新函数的闭包,用来在不修改原函数代码的前提下给函数增加额外功能。
3.2 最简单的装饰器:打印日志
def log_decorator(func): # func 是被装饰的原函数
def wrapper(*args, **kwargs):
print(f"调用函数 {func.__name__} 之前")
result = func(*args, **kwargs)
print(f"调用函数 {func.__name__} 之后")
return result
return wrapper
def say_hello():
print("Hello!")
say_hello = log_decorator(say_hello) # 手动装饰
say_hello()
输出:
调用函数 say_hello 之前
Hello!
调用函数 say_hello 之后
3.3 @语法糖:更优雅的写法
Python 提供了 @ 语法,直接放在函数定义上方,效果等同于手动调用装饰器。
@log_decorator
def say_hi():
print("Hi!")
say_hi() # 同样会被装饰
3.4 带参数的装饰器(简单版)
如果装饰器本身需要接收参数(比如日志前缀可自定义),只需在原来的外面再套一层函数。
def log_with_prefix(prefix): # 最外层,接收装饰器参数
def decorator(func): # 真正的装饰器
def wrapper(*args, **kwargs):
print(f"{prefix}: 执行前")
result = func(*args, **kwargs)
print(f"{prefix}: 执行后")
return result
return wrapper
return decorator
@log_with_prefix("[系统]")
def test():
print("测试功能")
test()
输出:
[系统]: 执行前
测试功能
[系统]: 执行后
3.5 @wraps 是干嘛的?
装饰后原函数的 __name__ 会变成 wrapper,这可能导致调试困难。使用 @wraps 可以让装饰后的函数保留原来的名字和文档信息。
from functools import wraps
def my_decorator(func):
@wraps(func) # 保留原函数的身份信息
def wrapper(*args, **kwargs):
print("装饰前")
return func(*args, **kwargs)
return wrapper
四、实战案例
4.1 计时器装饰器
计算函数执行耗时,是日常调试和性能分析的利器。
import time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函数 {func.name} 耗时 {end – start} 秒")
return result
return wrapper
@timer
def waste_time():
time.sleep(1) # 模拟耗时操作
return "完成"
print(waste_time())
输出:函数 waste_time 耗时 1.xxx 秒 然后打印 "完成"
4.2 登录验证装饰器
用装饰器来检查用户是否登录,这在 Web 开发中非常常用。
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 假设已登录用户存在变量 current_user 中
if not current_user.is_authenticated:
raise Exception("请先登录!")
return func(*args, **kwargs)
return wrapper
@login_required
def view_profile(user_id):
print(f"查看用户 {user_id} 的资料")
如果 current_user 未登录,调用 view_profile 就会报错
五、新手常见坑
5.1 忘记 return wrapper
装饰器函数必须返回内部定义的那个 wrapper 函数,否则原函数会变成 None。
# 错误写法
def bad_decorator(func):
def wrapper():
print("装饰一下")
return func()
# 忘了 return wrapper,结果返回 None
@bad_decorator
def my_func():
return "hello"
print(my_func) # 输出 None,悲剧了!
正确写法就是在装饰器函数末尾加上 return wrapper。
5.2 忘记 @wraps
from functools import wraps
def good_decorator(func):
@wraps(func) # 加上这一行
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result
return wrapper
@good_decorator
def original():
"""这是原函数的注释"""
pass
print(original.name) # 输出 original,不会变成 wrapper
print(original.doc) # 输出原注释
忘记加 @wraps 会导致调试、自动化生成文档时函数名混乱,一定记得加上。
六、总结
今天我们踩着两个阶梯快速入门了:闭包就是函数能记住外层作用域的变量,常用于创建计数器、函数工厂等;装饰器是闭包的升级应用,能优雅地给函数添加日志、计时、验证等附加功能。记住三点核心:装饰器本质是闭包、用 @ 语法糖更方便、一定加上 @wraps 保留原函数信息。你不是要成为大师,能看懂别人的代码,能自己写出简单的装饰器,就足够用了!





