欢迎来到 Python 进阶学习的第四天!今天,我们将深入探讨两个能让你的代码变得更加优雅、安全且易于复用的高级特性:装饰器 (Decorator) 和上下文管理器 (Context Manager)。
简单来说:
- 装饰器 → 让你无需修改原函数代码,就能为其“穿”上新的功能外衣,如日志、计时等。
- 上下文管理器 → 确保像文件、数据库连接这样的资源,其“获取、使用、释放”的整个生命周期都被妥善管理。
核心概念速览
| 技术 |
作用 |
典型场景 |
| 函数装饰器 |
在不修改原函数代码的前提下,增加功能 |
日志、计时、权限校验 |
| 类装饰器 |
用类实现装饰逻辑(适合有状态的装饰) |
缓存、重试计数 |
| 上下文管理器 |
确保资源“获取→使用→释放”完整生命周期 |
文件操作、数据库事务 |
实践一:编写一个实用的计时装饰器
目标:测量任意函数的执行耗时。
方式一:基础函数装饰器
# decorators.py
import time
from functools import wraps
def timer(func):
"""计时装饰器:打印函数执行时间"""
@wraps(func) # 保留原函数的元信息(如 __name__, __doc__)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 执行原函数
end = time.time()
print(f"⏱️ {func.__name__} 耗时: {end - start:.4f} 秒")
return result
return wrapper
# 使用示例
@timer
def slow_function():
time.sleep(1)
return "完成"
print(slow_function()) # 输出: ⏱️ slow_function 耗时: 1.0042 秒
关键点:
@wraps(func) 用于保留原函数的名称和文档字符串,否则函数名会变成 wrapper。
*args, **kwargs 让装饰器能够接受任意数量和类型参数,使其更具通用性。
方式二:带参数的装饰器(进阶)
有时候,我们想为装饰器传入一些配置,比如给计时器加个标签。
def timer_with_label(label="执行"):
"""可自定义标签的计时装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"⏱️ [{label}] {func.__name__}: {end - start:.4f} 秒")
return result
return wrapper
return decorator
# 使用
@timer_with_label("数据处理")
def process_data():
time.sleep(0.5)
process_data() # 输出: ⏱️ [数据处理] process_data: 0.5021 秒
实践二:了解类装饰器
当装饰逻辑需要保存状态(例如记录函数被调用了多少次)时,使用类来实现会更清晰。
class CountCalls:
"""类装饰器:统计函数调用次数"""
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"📞 {self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 📞 say_hello 已被调用 1 次
say_hello() # 📞 say_hello 已被调用 2 次
适用场景:需要保存状态(如计数、缓存)的装饰逻辑。
实践三:自定义文件读取上下文管理器
危险写法(资源可能未释放)
如果 open() 和 close() 之间发生异常,文件可能无法正常关闭,导致资源泄露。
def read_file_bad(filename):
f = open(filename)
data = f.read()
# 如果这里发生异常,f.close() 不会执行!
f.close()
return data
安全写法一:使用内置 with open
这是最推荐的方式,简洁且安全。
def read_file_good(filename):
with open(filename) as f:
return f.read()
# 自动调用 f.close()
安全写法二:自定义上下文管理器类
为了深入理解原理,我们可以自己实现一个。
# context_managers.py
class SafeFileReader:
"""自定义文件读取上下文管理器"""
def __init__(self, filename, mode='r', encoding="utf-8"):
if mode not in ('r', 'w', 'a', 'r+', 'w+', 'a+'):
raise ValueError(f"不支持的文件模式: {mode}")
self.filename = filename
self.mode = mode
self.encoding = encoding
self.file = None
def __enter__(self):
"""进入 with 块时调用,打开文件"""
print(f"📂 打开文件: {self.filename}")
self.file = open(self.filename, mode=self.mode, encoding=self.encoding)
return self.file # 返回给 as 变量
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出 with 块时调用(无论是否发生异常)"""
if self.file:
self.file.close()
print(f"🗑️ 关闭文件: {self.filename}")
# 返回 False 表示不抑制异常(默认行为)
return False
# 使用
try:
with SafeFileReader("test.txt") as f:
content = f.read()
print("内容:", content[:50])
except FileNotFoundError:
print("❌ 文件未找到")
__exit__ 参数说明:
exc_type:异常类型(如 FileNotFoundError)
exc_val:异常实例
exc_tb:traceback 对象
- 返回
True 可抑制异常(慎用!)
实战应用:结合银行系统场景
场景一:用装饰器记录关键操作耗时
在银行系统中,监控重要交易操作的耗时是常见需求,使用装饰器可以无侵入地实现。
# bank.py
from decorators import timer
class BankAccount:
# ...
@timer
def deposit(self, amount):
# 模拟复杂的业务校验(实际可能涉及风控、日志等)
time.sleep(0.01)
self._balance += amount
return True
场景二:用上下文管理器安全加载账户数据
从文件或数据库安全地加载账户信息,确保连接或文件句柄能被正确关闭。
# bank.py
from context_managers import SafeFileReader
import json
def load_accounts():
try:
with SafeFileReader("data/accounts.jsonl") as f:
for line in f:
yield json.loads(line.strip())
except FileNotFoundError:
logger.warning("账户文件不存在,返回空")
return
完整测试代码
# main.py
from decorators import timer, timer_with_label
from context_managers import SafeFileReader
# 测试装饰器
@timer
def calculate_sum(n):
return sum(range(n))
result = calculate_sum(1000000) # ⏱️ calculate_sum 耗时: 0.0421 秒
# 测试上下文管理器
with SafeFileReader("example.txt", "w") as f:
f.write("Hello, Context Manager!")
with SafeFileReader("example.txt") as f:
print("读取内容:", f.read()) # 输出: 📂 打开... Hello, Context Manager! 🗑️ 关闭...
关键知识点总结
-
装饰器本质
@decorator 只是语法糖,它的本质是函数调用。
@timer
def foo():
pass
# 等价于
foo = timer(foo)
-
上下文管理器协议
- 必须实现
__enter__ 和 __exit__ 两个方法。
with obj as x 语句等价于 x = obj.__enter__()。
- 退出
with 块时,会自动调用 obj.__exit__(),即使中间发生了异常。
-
何时使用?
| 需求 |
推荐方案 |
| 添加日志、计时、缓存等通用功能 |
函数装饰器 |
| 装饰逻辑需要保存状态(如调用次数、缓存数据) |
类装饰器 |
| 管理需要精确控制生命周期的资源(文件、锁、网络连接) |
上下文管理器 |
小结:迈向优雅与安全
掌握装饰器和上下文管理器,意味着你的 Python 代码在朝着专业和健壮的方向迈进。
- 装饰器 让功能增强像“穿衣服”一样简单,遵循了 DRY(Don‘t Repeat Yourself)原则。
- 上下文管理器 让资源管理像“呼吸”一样自然,有效防止了资源泄露。
这两项技术是 Python 函数式编程和资源管理范式的核心体现。希望今天的教程能帮助你更好地理解它们,并在你的下一个项目中灵活运用。如果你对更多 Python 高阶话题感兴趣,欢迎到 云栈社区 与大家交流探讨。