Python教程(二十五):装饰器–函数的高级用法
wptr33 2025-08-05 21:49 24 浏览
今天您将学习什么
- 什么是装饰器以及如何创建装饰器
- 函数装饰器和类装饰器
- 带参数的装饰器
- 装饰器的实际应用
- 真实世界示例:日志记录、性能监控、缓存、权限验证
什么是装饰器?
装饰器是Python中的一种设计模式,它允许您在不修改原函数代码的情况下,为函数添加新的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。
装饰器的优势:
- 代码复用:相同的功能可以应用到多个函数
- 代码分离:核心逻辑和横切关注点分离
- 可读性:使用@语法使代码更清晰
- 灵活性:可以动态地添加或移除功能
1. 基本装饰器
简单装饰器
def timer_decorator(func):
"""计时装饰器"""
import time
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行时间:{end_time - start_time:.4f}秒")
return result
return wrapper
def log_decorator(func):
"""日志装饰器"""
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
print(f"参数:args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"返回值:{result}")
return result
return wrapper
# 使用装饰器
@timer_decorator
@log_decorator
def fibonacci(n):
"""计算斐波那契数列"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 等价于:
# fibonacci = timer_decorator(log_decorator(fibonacci))
# 测试装饰器
print("计算斐波那契数列第5项:")
result = fibonacci(5)
print(f"结果:{result}")保留函数元信息
from functools import wraps
def preserve_metadata(func):
"""保留函数元信息的装饰器"""
@wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper
@preserve_metadata
def greet(name):
"""问候函数"""
return f"你好,{name}!"
# 测试元信息保留
print(f"函数名:{greet.__name__}")
print(f"函数文档:{greet.__doc__}")
print(f"函数调用:{greet('张三')}")2. 带参数的装饰器
装饰器工厂
def repeat(times):
"""重复执行装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(times):
print(f"第{i+1}次执行:")
result = func(*args, **kwargs)
return result
return wrapper
return decorator
def retry(max_attempts=3, delay=1):
"""重试装饰器"""
import time
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
print(f"最终失败:{e}")
raise
print(f"第{attempt + 1}次尝试失败:{e},{delay}秒后重试...")
time.sleep(delay)
return wrapper
return decorator
@repeat(3)
def say_hello(name):
"""问候函数"""
print(f"你好,{name}!")
return "问候完成"
@retry(max_attempts=3, delay=1)
def risky_function():
"""有风险的函数"""
import random
if random.random() < 0.7:
raise ValueError("随机错误")
return "成功!"
# 测试带参数的装饰器
print("=== 重复执行装饰器 ===")
say_hello("李四")
print("\n=== 重试装饰器 ===")
try:
result = risky_function()
print(f"结果:{result}")
except Exception as e:
print(f"最终失败:{e}")真实世界示例1:性能监控装饰器
import time
import functools
from collections import defaultdict
class PerformanceMonitor:
"""性能监控器"""
def __init__(self):
self.stats = defaultdict(list)
def monitor(self, func):
"""性能监控装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
start_memory = self._get_memory_usage()
try:
result = func(*args, **kwargs)
success = True
except Exception as e:
result = None
success = False
raise
finally:
end_time = time.time()
end_memory = self._get_memory_usage()
execution_time = end_time - start_time
memory_used = end_memory - start_memory
self.stats[func.__name__].append({
'execution_time': execution_time,
'memory_used': memory_used,
'success': success,
'timestamp': time.time()
})
return result
return wrapper
def _get_memory_usage(self):
"""获取内存使用量(简化版)"""
import psutil
try:
return psutil.Process().memory_info().rss / 1024 / 1024 # MB
except ImportError:
return 0
def get_stats(self, func_name=None):
"""获取统计信息"""
if func_name:
return self.stats.get(func_name, [])
return dict(self.stats)
def print_summary(self):
"""打印统计摘要"""
print("性能监控摘要:")
for func_name, calls in self.stats.items():
if calls:
times = [call['execution_time'] for call in calls]
memories = [call['memory_used'] for call in calls]
success_count = sum(1 for call in calls if call['success'])
print(f"\n{func_name}:")
print(f" 调用次数:{len(calls)}")
print(f" 成功次数:{success_count}")
print(f" 平均执行时间:{sum(times)/len(times):.4f}秒")
print(f" 最大执行时间:{max(times):.4f}秒")
print(f" 平均内存使用:{sum(memories)/len(memories):.2f}MB")
# 创建性能监控器实例
monitor = PerformanceMonitor()
@monitor.monitor
def slow_function():
"""慢函数"""
time.sleep(0.1)
return "完成"
@monitor.monitor
def fast_function():
"""快函数"""
return "快速完成"
@monitor.monitor
def error_function():
"""会出错的函数"""
raise ValueError("测试错误")
# 测试性能监控
print("=== 性能监控测试 ===")
for i in range(5):
slow_function()
fast_function()
try:
error_function()
except:
pass
# 打印统计信息
monitor.print_summary()真实世界示例2:缓存装饰器
import functools
import time
from collections import OrderedDict
class LRUCache:
"""LRU缓存装饰器"""
def __init__(self, max_size=128, ttl=None):
self.max_size = max_size
self.ttl = ttl # 生存时间(秒)
self.cache = OrderedDict()
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 创建缓存键
key = self._make_key(args, kwargs)
# 检查缓存
if key in self.cache:
value, timestamp = self.cache[key]
# 检查TTL
if self.ttl is None or time.time() - timestamp < self.ttl:
# 移动到末尾(最近使用)
self.cache.move_to_end(key)
print(f"缓存命中:{func.__name__}")
return value
else:
# 过期,删除
del self.cache[key]
# 计算新值
result = func(*args, **kwargs)
# 存储到缓存
self.cache[key] = (result, time.time())
# 如果缓存满了,删除最旧的
if len(self.cache) > self.max_size:
self.cache.popitem(last=False)
print(f"缓存未命中:{func.__name__}")
return result
return wrapper
def _make_key(self, args, kwargs):
"""创建缓存键"""
# 简化版:使用字符串表示
key_parts = [str(arg) for arg in args]
key_parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
return "|".join(key_parts)
def clear(self):
"""清空缓存"""
self.cache.clear()
def get_stats(self):
"""获取缓存统计"""
return {
'size': len(self.cache),
'max_size': self.max_size,
'keys': list(self.cache.keys())
}
# 使用LRU缓存
@LRUCache(max_size=10, ttl=60)
def fibonacci_cached(n):
"""带缓存的斐波那契函数"""
if n <= 1:
return n
return fibonacci_cached(n-1) + fibonacci_cached(n-2)
@LRUCache(max_size=5)
def expensive_calculation(x, y):
"""昂贵的计算"""
time.sleep(0.1) # 模拟耗时计算
return x * y + x + y
# 测试缓存
print("=== 缓存测试 ===")
print("计算斐波那契数列:")
for i in range(10):
result = fibonacci_cached(i)
print(f"fibonacci({i}) = {result}")
print("\n昂贵计算测试:")
for i in range(3):
for j in range(3):
result = expensive_calculation(i, j)
print(f"calc({i}, {j}) = {result}")
# 获取缓存统计
cache_decorator = fibonacci_cached.__closure__[0].cell_contents
print(f"\n缓存统计:{cache_decorator.get_stats()}")真实世界示例3:权限验证装饰器
from functools import wraps
from enum import Enum
class Permission(Enum):
"""权限枚举"""
READ = "read"
WRITE = "write"
DELETE = "delete"
ADMIN = "admin"
class User:
"""用户类"""
def __init__(self, username, permissions=None):
self.username = username
self.permissions = permissions or []
def has_permission(self, permission):
"""检查是否有指定权限"""
return permission in self.permissions or Permission.ADMIN in self.permissions
class PermissionDecorator:
"""权限验证装饰器"""
def __init__(self, required_permission):
self.required_permission = required_permission
def __call__(self, func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not isinstance(user, User):
raise ValueError("第一个参数必须是User对象")
if not user.has_permission(self.required_permission):
raise PermissionError(
f"用户 {user.username} 没有 {self.required_permission.value} 权限"
)
print(f"用户 {user.username} 执行 {func.__name__}")
return func(user, *args, **kwargs)
return wrapper
class FileManager:
"""文件管理器"""
@PermissionDecorator(Permission.READ)
def read_file(self, user, filename):
"""读取文件"""
return f"读取文件:{filename}"
@PermissionDecorator(Permission.WRITE)
def write_file(self, user, filename, content):
"""写入文件"""
return f"写入文件:{filename},内容:{content}"
@PermissionDecorator(Permission.DELETE)
def delete_file(self, user, filename):
"""删除文件"""
return f"删除文件:{filename}"
@PermissionDecorator(Permission.ADMIN)
def system_info(self, user):
"""系统信息(管理员权限)"""
return "系统信息:正常运行"
# 创建用户
admin_user = User("admin", [Permission.ADMIN])
editor_user = User("editor", [Permission.READ, Permission.WRITE])
reader_user = User("reader", [Permission.READ])
# 创建文件管理器
file_manager = FileManager()
# 测试权限验证
print("=== 权限验证测试 ===")
# 管理员测试
print("\n管理员操作:")
try:
print(file_manager.read_file(admin_user, "test.txt"))
print(file_manager.write_file(admin_user, "test.txt", "Hello"))
print(file_manager.delete_file(admin_user, "test.txt"))
print(file_manager.system_info(admin_user))
except Exception as e:
print(f"错误:{e}")
# 编辑者测试
print("\n编辑者操作:")
try:
print(file_manager.read_file(editor_user, "test.txt"))
print(file_manager.write_file(editor_user, "test.txt", "Hello"))
try:
print(file_manager.delete_file(editor_user, "test.txt"))
except PermissionError as e:
print(f"权限不足:{e}")
try:
print(file_manager.system_info(editor_user))
except PermissionError as e:
print(f"权限不足:{e}")
except Exception as e:
print(f"错误:{e}")
# 读者测试
print("\n读者操作:")
try:
print(file_manager.read_file(reader_user, "test.txt"))
try:
print(file_manager.write_file(reader_user, "test.txt", "Hello"))
except PermissionError as e:
print(f"权限不足:{e}")
try:
print(file_manager.delete_file(reader_user, "test.txt"))
except PermissionError as e:
print(f"权限不足:{e}")
try:
print(file_manager.system_info(reader_user))
except PermissionError as e:
print(f"权限不足:{e}")
except Exception as e:
print(f"错误:{e}")装饰器的最佳实践
推荐做法:
- 使用functools.wraps保留函数元信息
- 保持装饰器的简单性和可读性
- 合理使用装饰器参数
- 考虑装饰器的执行顺序
避免的做法:
- 创建过于复杂的装饰器
- 忽略函数元信息的保留
- 在装饰器中产生副作用
- 过度使用装饰器
高级装饰器特性
类装饰器
class Singleton:
"""单例装饰器"""
def __init__(self, cls):
self.cls = cls
self.instance = None
def __call__(self, *args, **kwargs):
if self.instance is None:
self.instance = self.cls(*args, **kwargs)
return self.instance
@Singleton
class Database:
"""数据库连接类"""
def __init__(self):
print("创建数据库连接...")
self.connection = "数据库连接"
def query(self, sql):
return f"执行查询:{sql}"
# 测试单例装饰器
db1 = Database()
db2 = Database()
print(f"db1 is db2: {db1 is db2}") # True装饰器链
def bold(func):
"""加粗装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
return f"**{func(*args, **kwargs)}**"
return wrapper
def italic(func):
"""斜体装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
return f"*{func(*args, **kwargs)}*"
return wrapper
@bold
@italic
def greet(name):
"""问候函数"""
return f"你好,{name}!"
# 测试装饰器链
print(greet("世界")) # **你好,世界!**- 上一篇: 玩转Python列表/字典:增删改查与高效遍历技巧
- 下一篇:Python中的数据类型操作
相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
-
- oracle数据导入导出_oracle数据导入导出工具
- 继续学习Python中的while true/break语句
- python continue和break的区别_python中break语句和continue语句的区别
- 简单学Python——关键字6——break和continue
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
- Python 中 break 和 continue 傻傻分不清
- python中的流程控制语句:continue、break 和 return使用方法
- L017:continue和break - 教程文案
- 作为前端开发者,你都经历过怎样的面试?
- 面试被问 const 是否不可变?这样回答才显功底
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
