前言
如果你已经熟悉了函数的基本用法,那么恭喜你来到Python进阶的大门!闭包(Closure)和装饰器(Decorator)是Python函数式编程的精髓,也是面试中绕不开的高频考点。本文将深入剖析闭包的“三要素”,并带你手写通用的日志装饰器、计时装饰器,让你真正理解@语法糖背后的原理。
1. 闭包(Closure)—— 函数工厂的基石
1.1 什么是闭包?
闭包本质上是一个嵌套函数,它让内层函数捕获并记住了外层函数的局部变量,即使外层函数已经执行完毕。
1.2 闭包三要素(面试必背)
函数嵌套:内层函数定义在外层函数内部。
内层函数使用外层函数的变量(捕获状态)。
外层函数返回内层函数(把“行为”返回出去)。
1.3 经典案例:计数器工厂
def counter(step):
count = 0 # 外部局部变量
def inner():
nonlocal count # 声明修改外部变量
count += step
return count
return inner
# 创建两个独立的计数器实例
c1 = counter(1)
c2 = counter(2)
print(c1()) # 1
print(c1()) # 2
print(c2()) # 2
print(c2()) # 4
每次调用counter,都会生成一个新的闭包空间,c1和c2的count互不干扰。
2. 装饰器(Decorator)—— 不修改代码,只增强功能
2.1 装饰器的本质
装饰器就是一个闭包,它接收一个函数作为参数,返回一个增强后的新函数。
语法糖 @deco 等价于 func = deco(func)。
2.2 第一个计时装饰器
在不改动原函数的情况下,计算函数执行时间:
import time
def timer(func):
def wrapper():
start = time.time()
func()
print(f"函数 {func.__name__} 耗时: {time.time() – start:.4f}s")
return wrapper
@timer
def sleep_func():
time.sleep(1)
sleep_func() # 函数 sleep_func 耗时: 1.0002s
2.3 通用装饰器(支持参数和返回值)
实际开发中,原函数往往有参数和返回值。我们需要用 *args, **kwargs 通吃一切。
def universal_deco(func):
def wrapper(*args, **kwargs):
print(f"正在执行 {func.__name__}")
result = func(*args, **kwargs) # 接收返回值
print("执行结束")
return result # 返回返回值
return wrapper
@universal_deco
def add(a, b):
return a + b
print(add(5, 3)) # 8 (同时打印了日志)
3. 多个装饰器叠加(执行顺序)
当多个装饰器叠在一起时,从下往上包装,调用时从上往下执行。
def deco1(func):
def wrapper():
print("开始装饰1")
func()
print("结束装饰1")
return wrapper
def deco2(func):
def wrapper():
print("开始装饰2")
func()
print("结束装饰2")
return wrapper
@deco1
@deco2
def core():
print("核心业务")
core()
# 输出顺序:
# 开始装饰1
# 开始装饰2
# 核心业务
# 结束装饰2
# 结束装饰1
4. 实战案例(一):排序算法时间开销统计
我们生成10000个随机数,对比 sorted()、list.sort()、冒泡排序、选择排序的效率。利用装饰器,零侵入地统计每个算法耗时。
import random
import time
# 准备4份相同的数据
data = [random.randint(1, 100000) for _ in range(10000)]
d1, d2, d3, d4 = data.copy(), data.copy(), data.copy(), data.copy()
def time_calc(func):
def wrapper():
start = time.time()
func()
print(f"{func.__name__} 耗时: {time.time() – start:.4f}s")
return wrapper
@time_calc
def test_sorted():
sorted(d1)
@time_calc
def test_sort():
d2.sort()
@time_calc
def test_bubble():
# 冒泡排序(优化版)
for i in range(len(d3)-1):
for j in range(len(d3)-i-1):
if d3[j] > d3[j+1]:
d3[j], d3[j+1] = d3[j+1], d3[j]
@time_calc
def test_selection():
# 选择排序
for i in range(len(d4)-1):
min_idx = i
for j in range(i+1, len(d4)):
if d4[j] < d4[min_idx]:
min_idx = j
if min_idx != i:
d4[i], d4[min_idx] = d4[min_idx], d4[i]
test_sorted() # 极快
test_sort() # 极快
test_bubble() # 龟速(慎用,可能要几十秒)
test_selection() # 较慢
运行后你会直观感受到算法时间复杂度带来的性能差异,这正是装饰器在性能监控中的典型应用。
5. 实战案例(二):日志记录装饰器(保存到文件)
利用 datetime 记录函数执行的时间、参数和返回值,并写入TXT日志。
from datetime import datetime
def logger(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
# 拼接日志内容
log_msg = (f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | "
f"函数: {func.__name__} | "
f"参数: {args} {kwargs} | "
f"返回值: {result}\\n")
# 写入文件(追加模式)
with open("func_log.txt", "a", encoding="utf-8") as f:
f.write(log_msg)
return result
return wrapper
@logger
def max_value(a, b):
return a if a > b else b
max_value(10, 20)
max_value(100, 50)
打开同目录下的 func_log.txt,你会看到清晰的调用记录。
6. 装饰器带参数(三层嵌套)
有时我们希望装饰器本身能接收参数(如指定日志文件名),需要再包一层。
def file_logger(filename):
def decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
with open(filename, "a") as f:
f.write(f"{func.__name__} 执行完毕\\n")
return result
return wrapper
return decorator
@file_logger("app.log")
def run_task():
print("任务进行中…")
run_task()
这种“装饰器工厂”写法在实际框架(如Flask路由)中非常常见。
结语
闭包和装饰器是Python进阶路上绕不开的两座大山。理解闭包的三要素,你就能看懂一切装饰器的源码;掌握装饰器的嵌套机制,你就能写出高可用的日志、权限、缓存、计时等通用组件。
建议你亲手把文中的代码敲一遍,观察控制台输出和文件写入结果。只有动手实践,才能真正内化这些知识!
如果这篇文章帮到了你,欢迎一键三连,我们下期见!
网硕互联帮助中心





评论前必须登录!
注册