Python教程19:装饰器入门
“简洁的力量在于抽象的恰当。”
装饰器是Python最强大的特性之一,也是初学者觉得最"神秘"的部分。今天我们从基础开始,逐步揭开装饰器的面纱,理解它的本质和应用。
1. 什么是装饰器
问题场景
假设你有多个函数,需要在每个函数执行前后记录日志:
1def add(a, b):
2 print("函数开始执行") # 重复代码
3 result = a + b
4 print("函数执行完毕") # 重复代码
5 return result
6
7def multiply(a, b):
8 print("函数开始执行") # 重复代码
9 result = a * b
10 print("函数执行完毕") # 重复代码
11 return result
问题:每个函数都要写重复的日志代码,违反了DRY(Don’t Repeat Yourself)原则。
装饰器的解决方案
装饰器是一种设计模式,用于在不修改原函数代码的情况下,给函数添加新功能。就像给礼物包装一样,外面包了一层,但礼物本身没变。
1@log_decorator # 这就是装饰器
2def add(a, b):
3 return a + b
4
5# 调用时自动有日志功能
6add(3, 5)
7# 输出:
8# 函数开始执行
9# 8
10# 函数执行完毕
2. 理解装饰器的基础:闭包
在学习装饰器前,需要先理解闭包(Closure)。
什么是闭包
闭包是指:一个函数内部定义的函数,可以访问外部函数的变量。
1def outer(x):
2 # x是外部函数的变量
3
4 def inner(y):
5 # inner可以访问outer的变量x
6 return x + y
7
8 return inner # 返回内部函数
9
10# 创建闭包
11add_5 = outer(5)
12print(add_5(3)) # 8(3 + 5)
13print(add_5(10)) # 15(10 + 5)
关键点:
inner函数"记住"了外部函数的变量x- 即使
outer执行完毕,x的值仍然保留 - 这就是"闭包"——函数+环境
闭包的实际应用
1def make_multiplier(n):
2 """创建一个乘法器"""
3 def multiplier(x):
4 return x * n
5 return multiplier
6
7# 创建不同的乘法器
8times_3 = make_multiplier(3)
9times_5 = make_multiplier(5)
10
11print(times_3(10)) # 30
12print(times_5(10)) # 50
为什么需要闭包:
- 数据封装:变量
n被"保护"在闭包内 - 工厂模式:根据参数创建不同的函数
- 装饰器的基础:装饰器本质上就是闭包
3. 第一个装饰器
手动实现装饰器
1def my_decorator(func):
2 """
3 这是一个装饰器函数
4 - 接收一个函数作为参数
5 - 返回一个新的函数
6 """
7 def wrapper(*args, **kwargs):
8 print("函数执行前")
9 result = func(*args, **kwargs) # 调用原函数
10 print("函数执行后")
11 return result
12 return wrapper
13
14# 不使用@语法
15def say_hello():
16 print("Hello!")
17
18# 手动装饰
19say_hello = my_decorator(say_hello)
20
21say_hello()
22# 输出:
23# 函数执行前
24# Hello!
25# 函数执行后
使用@语法糖
Python提供了@符号作为语法糖,让装饰器使用更简洁:
1@my_decorator # 等价于:say_hello = my_decorator(say_hello)
2def say_hello():
3 print("Hello!")
4
5say_hello()
@符号的本质:
@my_decorator是一个快捷方式- 在函数定义后立即调用
my_decorator(say_hello) - 用返回的新函数替换原函数
4. 带参数的装饰器
装饰带参数的函数
1def log_decorator(func):
2 """记录函数调用的装饰器"""
3 def wrapper(*args, **kwargs):
4 print(f"调用函数:{func.__name__}")
5 print(f"参数:args={args}, kwargs={kwargs}")
6 result = func(*args, **kwargs)
7 print(f"返回值:{result}")
8 return result
9 return wrapper
10
11@log_decorator
12def add(a, b):
13 return a + b
14
15result = add(3, 5)
16# 输出:
17# 调用函数:add
18# 参数:args=(3, 5), kwargs={}
19# 返回值:8
*args, **kwargs的作用:
- 让wrapper能接收any参数
- 透传给原函数
- 保持装饰器的通用性
装饰器本身带参数
如果装饰器需要配置参数,需要再包一层函数:
1def repeat(times):
2 """
3 可配置的装饰器
4 - times: 重复执行次数
5 - 这是装饰器工厂,返回真正的装饰器
6 """
7 def decorator(func):
8 def wrapper(*args, **kwargs):
9 for _ in range(times):
10 result = func(*args, **kwargs)
11 return result
12 return wrapper
13 return decorator
14
15@repeat(times=3) # 先调用repeat(3)得到装饰器,再装饰say_hello
16def say_hello():
17 print("Hello!")
18
19say_hello()
20# 输出:
21# Hello!
22# Hello!
23# Hello!
三层结构:
repeat(times):接收配置参数decorator(func):真正的装饰器wrapper(*args, **kwargs):包装函数
5. 实用装饰器示例
计时装饰器
1import time
2
3def timer(func):
4 """
5 测量函数执行时间的装饰器
6 - 使用time模块(Python标准库)
7 - time.time()返回当前时间戳(秒)
8 """
9 def wrapper(*args, **kwargs):
10 start = time.time()
11 result = func(*args, **kwargs)
12 end = time.time()
13 print(f"{func.__name__}执行时间:{end - start:.4f}秒")
14 return result
15 return wrapper
16
17@timer
18def slow_function():
19 time.sleep(2) # 模拟耗时操作
20 return "完成"
21
22slow_function()
23# 输出:slow_function执行时间:2.0001秒
缓存装饰器
1def cache(func):
2 """
3 简单的缓存装饰器
4 - 使用字典存储已计算的结果
5 - 避免重复计算,提升性能
6 - Python标准库有functools.lru_cache,功能更强
7 """
8 cached_results = {}
9
10 def wrapper(*args):
11 if args in cached_results:
12 print(f"从缓存返回:{args}")
13 return cached_results[args]
14
15 result = func(*args)
16 cached_results[args] = result
17 return result
18
19 return wrapper
20
21@cache
22def fibonacci(n):
23 """计算斐波那契数"""
24 if n <= 1:
25 return n
26 return fibonacci(n-1) + fibonacci(n-2)
27
28print(fibonacci(10)) # 第一次计算
29print(fibonacci(10)) # 第二次从缓存返回
权限检查装饰器
1def require_auth(func):
2 """
3 权限检查装饰器
4 - 模拟需要登录才能执行的函数
5 - Web开发中常用于路由保护
6 """
7 def wrapper(*args, **kwargs):
8 # 假设这里检查用户是否登录
9 is_authenticated = False # 模拟未登录
10
11 if not is_authenticated:
12 print("错误:需要登录")
13 return None
14
15 return func(*args, **kwargs)
16
17 return wrapper
18
19@require_auth
20def view_profile():
21 return "个人资料页面"
22
23view_profile() # 输出:错误:需要登录
6. 多个装饰器
可以给一个函数应用多个装饰器:
1@decorator1
2@decorator2
3@decorator3
4def my_function():
5 pass
6
7# 等价于:
8# my_function = decorator1(decorator2(decorator3(my_function)))
执行顺序:从下到上装饰,从上到下执行。
1def bold(func):
2 def wrapper():
3 return "<b>" + func() + "</b>"
4 return wrapper
5
6def italic(func):
7 def wrapper():
8 return "<i>" + func() + "</i>"
9 return wrapper
10
11@bold
12@italic
13def greet():
14 return "Hello"
15
16print(greet()) # <b><i>Hello</i></b>
7. 保持函数元信息
装饰后,函数的元信息会丢失:
1def my_decorator(func):
2 def wrapper(*args, **kwargs):
3 return func(*args, **kwargs)
4 return wrapper
5
6@my_decorator
7def my_function():
8 """这是我的函数"""
9 pass
10
11print(my_function.__name__) # wrapper(而不是my_function)
12print(my_function.__doc__) # None(文档丢失)
解决方案:使用functools.wraps
1from functools import wraps
2
3def my_decorator(func):
4 @wraps(func) # 保留原函数的元信息
5 def wrapper(*args, **kwargs):
6 return func(*args, **kwargs)
7 return wrapper
8
9@my_decorator
10def my_function():
11 """这是我的函数"""
12 pass
13
14print(my_function.__name__) # my_function
15print(my_function.__doc__) # 这是我的函数
functools.wraps的作用:
- 复制原函数的
__name__、__doc__等属性 - 让装饰后的函数看起来像原函数
- 是装饰器的最佳实践
8. 类装饰器
除了函数,类也可以作为装饰器:
1class CountCalls:
2 """
3 统计函数调用次数的装饰器(类版本)
4 - 使用类可以更好地管理状态
5 - __init__接收被装饰的函数
6 - __call__让对象可以像函数一样调用
7 """
8 def __init__(self, func):
9 self.func = func
10 self.count = 0
11
12 def __call__(self, *args, **kwargs):
13 self.count += 1
14 print(f"调用次数:{self.count}")
15 return self.func(*args, **kwargs)
16
17@CountCalls
18def say_hello():
19 print("Hello!")
20
21say_hello() # 调用次数:1
22say_hello() # 调用次数:2
9. 小结
今天我们学习了装饰器:
- 本质:函数接收函数,返回函数
- 基础:闭包(函数+环境变量)
- 语法:
@decorator是语法糖 - 带参数:需要三层函数结构
- 实用示例:计时、缓存、权限检查
- 多装饰器:从下到上装饰
- 最佳实践:使用
@wraps保持元信息 - 类装饰器:用类实现装饰器
装饰器是Python的高级特性,掌握它能让代码更优雅、更模块化。
练习题:
- 写一个装饰器,记录函数被调用的时间和参数
- 实现一个重试装饰器,函数失败时自动重试3次
- 写一个装饰器,限制函数执行频率(如每秒最多1次)
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
