请详细说明Python3中装饰器的用法 #
包括基本概念、语法、应用场景以及高级用法。
1. 装饰器的基本概念 #
装饰器是Python中的一种设计模式,它允许我们在不修改原函数代码的情况下,动态地增强或修改函数、方法或类的功能。装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。
2. 基本装饰器语法 #
装饰器的基本语法主要包括三部分:
定义装饰器函数
装饰器通常是一个函数,参数为另一个函数对象,并返回一个新的函数。在目标函数上应用装饰器
可通过@装饰器名语法糖,直接将装饰器作用于目标函数。返回包装后的新函数
装饰器用内部函数对原函数进行包装,在内部可以加入自定义功能。
基本结构示例:
def decorator_name(func):
def wrapper(*args, **kwargs):
# 在调用原始函数前可加入自定义操作
print("开始执行")
result = func(*args, **kwargs)
# 在调用原始函数后可加入自定义操作
print("执行结束")
return result
return wrapper
@decorator_name # 等效于 func = decorator_name(func)
def func():
print("这是原始函数内容")
func() function decoratorName(func) {
return function(...args) {
// 在调用原始函数前可加入自定义操作
console.log("开始执行");
const result = func(...args);
// 在调用原始函数后可加入自定义操作
console.log("执行结束");
return result;
}
}
// 等效于 func = decoratorName(func)
function func() {
console.log("这是原始函数内容");
}
const decoratedFunc = decoratorName(func);
decoratedFunc();这样,调用 func() 时,实际执行的是装饰器包装后的新功能。
2.1 简单装饰器示例 #
# 定义一个简单的装饰器函数
def my_decorator(func):
# 定义内部包装函数
def wrapper():
# 在函数调用前执行的操作
print("函数调用前:正在准备执行...")
# 调用原始函数
func()
# 在函数调用后执行的操作
print("函数调用后:执行完成!")
# 返回包装函数
return wrapper
# 使用@语法糖应用装饰器
@my_decorator
def say_hello():
# 原始函数功能
print("Hello, World!")
# 调用被装饰的函数
say_hello()输出结果:
函数调用前:正在准备执行...
Hello, World!
函数调用后:执行完成!2.2 带参数的装饰器 #
# 定义装饰器工厂函数,接受参数
def repeat(num_times):
# 返回实际的装饰器函数
def decorator(func):
# 定义包装函数,处理参数传递
def wrapper(*args, **kwargs):
# 根据指定次数重复执行函数
for i in range(num_times):
print(f"第 {i+1} 次执行:")
# 调用原始函数并传递所有参数
result = func(*args, **kwargs)
# 返回最后一次执行的结果
return result
# 返回包装函数
return wrapper
# 返回装饰器函数
return decorator
# 使用带参数的装饰器
@repeat(3)
def greet(name):
# 问候函数
print(f"Hello {name}!")
# 返回问候信息
return f"Greeting for {name}"
# 调用被装饰的函数
result = greet("Python")
print(f"最终结果:{result}")输出结果:
第 1 次执行:
Hello Python!
第 2 次执行:
Hello Python!
第 3 次执行:
Hello Python!
最终结果:Greeting for Python3. 类装饰器 #
类装饰器就是用类来实现装饰器功能,通常通过定义__call__方法,使其实例像函数一样可以被调用,从而在方法执行前后添加自定义逻辑。类装饰器的优点在于可以保存状态(比如计数、缓存)和更强的扩展性。
核心原理:
- 类装饰器接收一个函数(或类),返回一个新的可调用对象(通常是其本身的实例)。
- 通过实现
__init__和__call__方法,自定义装饰逻辑。
基本用法举例:
# 定义一个类装饰器Logger
class Logger:
# 初始化方法,接收被装饰的函数
def __init__(self, func):
# 保存被装饰的函数到实例属性
self.func = func
# 定义__call__方法,使实例可以像函数一样被调用
def __call__(self, *args, **kwargs):
# 打印函数开始执行的信息
print(f"开始执行:{self.func.__name__}")
# 调用被装饰的函数并保存结果
result = self.func(*args, **kwargs)
# 打印函数结束执行的信息
print(f"结束执行:{self.func.__name__}")
# 返回函数的返回值
return result
# 使用类装饰器Logger来装饰函数say_hello
@Logger
# 定义一个打印问候语的函数
def say_hello(name):
# 打印问候信息
print(f"Hello, {name}!")
# 调用被装饰的say_hello函数
say_hello("Python")输出结果:
开始执行:say_hello
Hello, Python!
结束执行:say_hello4. 装饰类的装饰器 #
装饰类的装饰器(Class Decorator for Classes),即“修饰一个类本身的装饰器”,用于动态地为类增加属性、方法,甚至修改类的行为。这类装饰器的参数是“类”,返回值通常是修改或扩充后的类本身。常见用途包括:为所有实例增加某种通用功能、注入日志、权限检查、单例模式实现等。
核心原理:
- 类装饰器的参数是类对象本身(不是实例、不是函数)。
- 可以直接操作类本身,比如动态添加属性或方法、修改父类、返回经过更改的新类。
基本用法举例:
# 定义一个单例模式的类装饰器
def singleton(cls):
# 用于保存类的实例
_instance = {}
# 定义返回实例的函数
def get_instance(*args, **kwargs):
# 如果实例不存在,则创建一个新的实例
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
# 返回实例
return _instance[cls]
# 返回获取实例的函数
return get_instance
# 使用singleton装饰器修饰MyDatabase类
@singleton
class MyDatabase:
# 初始化方法
def __init__(self):
# 输出初始化信息
print("初始化数据库连接")
# 创建第一个数据库实例
db1 = MyDatabase()
# 创建第二个数据库实例
db2 = MyDatabase()
# 判断db1和db2是否为同一对象,并输出结果
print(db1 is db2) # True输出结果:
初始化数据库连接
True此外,也可以用类装饰器为类注入通用功能,例如动态添加新方法:
# 定义一个装饰器函数 add_say_hi,参数为类对象 cls
def add_say_hi(cls):
# 在装饰器内部定义一个实例方法 say_hi,参数为实例 self
def say_hi(self):
# 打印出带有类名的问候语
print(f"Hi,我是{self.__class__.__name__}")
# 将 say_hi 方法赋值给类 cls,使其成为该类的实例方法
cls.say_hi = say_hi
# 返回被修改过的类 cls
return cls
# 使用 add_say_hi 装饰 People 类,给类动态添加 say_hi 方法
@add_say_hi
class People:
# 定义一个空类 People
pass
# 实例化 People 类,创建对象 p
p = People()
# 调用 p 对象的 say_hi 方法,输出 Hi,我是People
p.say_hi() # Hi,我是People注意事项:
- 类装饰器不要误用于实例;本质是“接收一个类,返回一个类或可调用对象”。
- 和函数装饰器一样,可链式叠加,装饰多个功能。
5. 使用functools.wraps保持元数据 #
在使用装饰器时,通常会希望被装饰函数的元信息(如函数名、文档字符串、注释等)能够保留,否则被装饰后函数的__name__、__doc__等属性会丢失。为了解决这个问题,Python标准库提供了functools.wraps装饰器。它的作用是将原函数的重要元数据复制到包装函数wrapper上。
用法说明:
- 在自定义装饰器的内部包装函数外层,加上
@wraps(func),这样包装函数就能“伪装”成原来的函数。 - 这对于调试、文档生成、元编程等场景尤其重要。
# 导入functools模块中的wraps装饰器
from functools import wraps
# 定义保持元数据的装饰器
def my_decorator(func):
# 使用wraps装饰器保持原函数的元数据
@wraps(func)
def wrapper(*args, **kwargs):
# 在函数执行前打印信息
print("函数执行前:开始处理...")
# 调用原始函数并获取返回值
result = func(*args, **kwargs)
# 在函数执行后打印信息
print("函数执行后:处理完成!")
# 返回原始函数的返回值
return result
# 返回包装函数
return wrapper
# 使用装饰器
@my_decorator
def calculate_sum(a, b):
"""
计算两个数的和
Args:
a (int): 第一个数
b (int): 第二个数
Returns:
int: 两数之和
"""
# 计算并返回两数之和
return a + b
# 调用被装饰的函数
result = calculate_sum(5, 3)
print(f"计算结果:{result}")
# 验证元数据是否保持
print(f"函数名:{calculate_sum.__name__}")
print(f"函数文档:{calculate_sum.__doc__}")
print(f"函数模块:{calculate_sum.__module__}")输出结果:
函数执行前:开始处理...
函数执行后:处理完成!
计算结果:8
函数名:calculate_sum
函数文档:
计算两个数的和
Args:
a (int): 第一个数
b (int): 第二个数
Returns:
int: 两数之和
函数模块:__main__6. 实际应用场景示例 #
6.1 性能测试装饰器 #
# 导入时间模块
import time
# 从functools模块导入wraps函数
from functools import wraps
# 定义性能测试装饰器函数,接收一个参数func
def performance_test(func):
# 使用wraps装饰,使包装函数保留原函数信息
@wraps(func)
# 定义包装函数,接受任意位置和关键字参数
def wrapper(*args, **kwargs):
# 记录函数开始执行的时间
start_time = time.time()
# 调用原始函数并获取其返回值
result = func(*args, **kwargs)
# 记录函数结束执行的时间
end_time = time.time()
# 计算函数的执行时长
execution_time = end_time - start_time
# 打印函数名称及其执行时间
print(f"函数 {func.__name__} 执行时间:{execution_time:.4f} 秒")
# 返回原始函数的执行结果
return result
# 返回作为装饰器的包装函数
return wrapper
# 使用performance_test装饰器装饰slow_function函数
@performance_test
# 定义一个模拟耗时操作的函数,接受一个参数n
def slow_function(n):
# 初始化total为0
total = 0
# 遍历从0到n-1的所有整数
for i in range(n):
# 每次循环将i累加到total中
total += i
# 返回累加结果
return total
# 调用slow_function函数,并将结果赋值给result变量
result = slow_function(1000000)
# 打印slow_function函数的计算结果
print(f"计算结果:{result}")6.2 缓存装饰器 #
# 导入 wraps,用于保持原函数的元数据
from functools import wraps
# 定义一个全局的缓存字典
cache = {}
# 定义一个缓存装饰器函数
def cache_result(func):
# 使用 @wraps 保证被装饰函数的签名信息不变
@wraps(func)
def wrapper(*args, **kwargs):
# 基于输入参数生成唯一的缓存键
cache_key = str(args) + str(sorted(kwargs.items()))
# 如果缓存中已有结果,则直接返回缓存内容
if cache_key in cache:
print(f"从缓存获取结果:{cache_key}")
return cache[cache_key]
# 如果缓存中没有结果,则计算函数结果
print(f"计算新结果:{cache_key}")
result = func(*args, **kwargs)
# 将新计算的结果存入缓存
cache[cache_key] = result
# 返回计算得到的结果
return result
# 返回包装后的新函数
return wrapper
# 使用缓存装饰器装饰 fibonacci 函数
@cache_result
def fibonacci(n):
# 如果 n 小于等于 1,直接返回 n
if n <= 1:
return n
# 否则递归计算前两个斐波那契数的和
return fibonacci(n-1) + fibonacci(n-2)
# 打印第一次调用 fibonacci(10) 的结果
print(f"fibonacci(10) = {fibonacci(10)}")
# 打印第二次调用 fibonacci(10) 的结果,会使用缓存
print(f"fibonacci(10) = {fibonacci(10)}")6.3 日志记录装饰器 #
# 导入日志模块
import logging
# 从functools模块导入wraps装饰器
from functools import wraps
# 从datetime模块导入datetime类
from datetime import datetime
# 配置日志基本设置
logging.basicConfig(
# 设置日志记录级别为INFO
level=logging.INFO,
# 设置日志输出格式
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
# 配置日志处理器,输出到文件和终端
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
# 定义用于函数调用日志记录的装饰器
def log_function_call(func):
# 使用wraps保留原始函数的元数据
@wraps(func)
def wrapper(*args, **kwargs):
# 获取被装饰函数的名称
func_name = func.__name__
# 记录函数调用开始的信息,包括参数
logging.info(f"开始调用函数: {func_name}, 参数: args={args}, kwargs={kwargs}")
try:
# 调用实际的被装饰函数
result = func(*args, **kwargs)
# 记录函数调用成功和返回值
logging.info(f"函数 {func_name} 调用成功,返回值: {result}")
# 返回被装饰函数的结果
return result
except Exception as e:
# 发生异常时,记录异常信息
logging.error(f"函数 {func_name} 调用失败,异常: {str(e)}")
# 抛出异常
raise
# 返回包装后的函数
return wrapper
# 使用日志记录装饰器装饰divide_numbers函数
@log_function_call
def divide_numbers(a, b):
# 判断除数是否为零,如为零则抛出异常
if b == 0:
raise ValueError("除数不能为零")
# 返回除法计算结果
return a / b
# 测试divide_numbers函数的正常情况
try:
# 调用divide_numbers进行10除以2
result = divide_numbers(10, 2)
# 打印计算结果
print(f"计算结果: {result}")
except ValueError as e:
# 捕获并打印错误信息
print(f"错误: {e}")
# 测试divide_numbers函数的异常情况
try:
# 调用divide_numbers进行10除以0,会抛出异常
result = divide_numbers(10, 0)
except ValueError as e:
# 捕获并打印错误信息
print(f"错误: {e}")6.4 权限验证装饰器 #
# 导入wraps用于保持被装饰函数的元数据
from functools import wraps
# 定义角色类
class Role:
# 初始化方法,name为角色名,permissions为权限列表(可选)
def __init__(self, name, permissions=None):
# 保存角色名
self.name = name
# 如果没有提供权限列表,则默认为空列表
self.permissions = permissions or []
# 为角色添加权限
def add_permission(self, permission):
# 如果权限尚未存在于权限列表中,则添加
if permission not in self.permissions:
self.permissions.append(permission)
# 检查角色是否拥有某个权限
def has_permission(self, permission):
# 如果权限在权限列表中,则返回True
return permission in self.permissions
# 定义用户类
class User:
# 初始化方法,name为用户名,role为用户角色(可选)
def __init__(self, name, role=None):
# 保存用户名
self.name = name
# 记录用户的角色
self.role = role
# 检查用户是否拥有指定权限,通过其角色判断
def has_permission(self, permission):
# 如果用户有关联的角色,则调用角色的has_permission
if self.role:
return self.role.has_permission(permission)
# 如果没有角色,直接返回False
return False
# 定义权限检查函数
def check_permission(user, required_permission):
# 检查用户是否有指定权限
return user.has_permission(required_permission)
# 定义权限验证装饰器
def require_permission(permission):
# 装饰器工厂,接收需要的权限
def decorator(func):
# 使用wraps保持函数元数据
@wraps(func)
def wrapper(*args, **kwargs):
# 判断参数是否符合要求,第二个参数应为用户对象
if len(args) >= 2 and hasattr(args[1], 'has_permission'):
user = args[1]
# 检查用户是否有所需权限
if not check_permission(user, permission):
# 获取角色名,如果没有角色则为“无角色”
role_name = user.role.name if user.role else "无角色"
# 没有权限时抛出PermissionError异常
raise PermissionError(f"用户 {user.name} (角色: {role_name}) 没有权限 {permission}")
else:
# 参数错误时抛出ValueError异常
raise ValueError("函数需要用户对象作为第二个参数")
# 权限校验通过,调用原始函数
return func(*args, **kwargs)
# 返回包装后的函数
return wrapper
# 返回装饰器
return decorator
# 定义管理面板类
class AdminPanel:
# 删除用户方法,需delete_user权限
@require_permission('delete_user')
def delete_user(self, user, target_user):
# 返回删除结果字符串
return f"用户 {target_user} 已被 {user.name} 删除"
# 查看资料方法,需view_profile权限
@require_permission('view_profile')
def view_profile(self, user, target_user):
# 返回查看资料结果字符串
return f"用户 {user.name} 正在查看 {target_user} 的资料"
# 创建管理员角色,并赋予删除和查看权限
admin_role = Role("管理员", [ 'delete_user', 'view_profile'])
# 创建普通用户角色,仅赋予查看权限
user_role = Role("普通用户", [ 'view_profile'])
# 创建管理员用户,关联管理员角色
admin_user = User("admin", admin_role)
# 创建普通用户,关联普通用户角色
normal_user = User("user1", user_role)
# 打印测试开始信息
print("开始测试权限验证...")
# 创建管理面板实例
panel = AdminPanel()
# 测试管理员删除用户
print("测试管理员删除用户...")
try:
# 尝试用管理员删除用户
result = panel.delete_user(admin_user, "test_user")
# 打印删除结果
print(result)
except PermissionError as e:
# 捕获权限异常并输出
print(f"权限错误: {e}")
# 测试普通用户删除用户(应无权限)
print("测试普通用户删除用户...")
try:
# 尝试用普通用户删除用户(应该没有权限)
result = panel.delete_user(normal_user, "test_user")
# 打印结果
print(result)
except PermissionError as e:
# 捕获权限异常并输出
print(f"权限错误: {e}")
# 测试普通用户查看资料(应有权限)
print("测试普通用户查看资料...")
try:
# 尝试用普通用户查看资料(应该有权限)
result = panel.view_profile(normal_user, "test_user")
# 打印查看结果
print(result)
except PermissionError as e:
# 捕获权限异常并输出
print(f"权限错误: {e}")
# 展示角色权限管理的优势
print("\n=== 角色权限管理演示 ===")
# 打印管理员角色的权限
print(f"管理员角色权限: {admin_role.permissions}")
# 打印普通用户角色的权限
print(f"普通用户角色权限: {user_role.permissions}")
# 动态为普通用户角色添加删除权限
print("\n为普通用户角色添加删除权限...")
# 调用方法为普通用户角色添加delete_user权限
user_role.add_permission('delete_user')
# 打印更新后的权限列表
print(f"更新后普通用户角色权限: {user_role.permissions}")
# 测试普通用户删除用户(更新权限后,应有权限)
print("\n测试普通用户删除用户(更新权限后)...")
try:
# 普通用户删除用户,此时应有权限
result = panel.delete_user(normal_user, "test_user")
# 打印结果
print(result)
except PermissionError as e:
# 捕获权限异常并输出
print(f"权限错误: {e}")
# 创建无角色用户,测试其权限
print("\n测试无角色用户...")
# 新建未关联角色的用户
no_role_user = User("guest", None)
try:
# 尝试用无角色用户查看资料
result = panel.view_profile(no_role_user, "test_user")
# 打印结果
print(result)
except PermissionError as e:
# 捕获权限异常并输出
print(f"权限错误: {e}")6.5 事务处理装饰器 #
# 导入wraps,用于装饰器保留原函数元信息
from functools import wraps
# 定义一个模拟数据库连接的类
class DatabaseConnection:
# 定义初始化方法,设置连接和事务的状态
def __init__(self):
# 初始化数据库连接对象(模拟,此处为None)
self.connection = None
# 初始化事务激活标记为False
self.transaction_active = False
# 定义开始事务的方法
def begin_transaction(self):
# 设置事务激活标志为True
self.transaction_active = True
# 打印事务开始的信息
print("开始数据库事务")
# 定义提交事务的方法
def commit_transaction(self):
# 如果事务已激活
if self.transaction_active:
# 设置事务激活标记为False
self.transaction_active = False
# 打印提交事务的信息
print("提交数据库事务")
# 定义回滚事务的方法
def rollback_transaction(self):
# 如果事务已激活
if self.transaction_active:
# 设置事务激活标记为False
self.transaction_active = False
# 打印回滚事务的信息
print("回滚数据库事务")
# 定义执行SQL语句的方法
def execute_sql(self, sql, params=None):
# 如果事务没有激活,抛出异常
if not self.transaction_active:
raise Exception("必须在事务中执行SQL")
# 打印要执行的SQL语句
print(f"执行SQL: {sql}")
# 如果有参数,打印参数列表
if params:
print(f"参数: {params}")
# 返回True,表示执行成功(模拟)
return True
# 定义更新账户余额的方法
def update_account_balance(self, account_id, amount_change):
# 构造SQL语句,用于更新账户余额
sql = f"UPDATE accounts SET balance = balance + {amount_change} WHERE account_id = ?"
# 执行SQL语句,并传入参数
return self.execute_sql(sql, (account_id,))
# 创建数据库连接对象,作为全局对象
db = DatabaseConnection()
# 定义数据库事务处理的装饰器
def database_transaction(func):
# 保留原函数的元信息
@wraps(func)
def wrapper(*args, **kwargs):
# 开始数据库事务
db.begin_transaction()
try:
# 调用被装饰的函数
result = func(*args, **kwargs)
# 调用成功,提交事务
db.commit_transaction()
# 返回原函数的结果
return result
except Exception as e:
# 调用过程中出现异常,回滚事务
db.rollback_transaction()
# 打印事务回滚的原因
print(f"事务回滚,原因: {str(e)}")
# 再次抛出异常
raise
# 返回包装后的函数
return wrapper
# 使用事务处理装饰器修饰转账函数
@database_transaction
def transfer_money(from_account, to_account, amount):
# 打印转账操作的信息
print(f"从账户 {from_account} 转账 {amount} 到账户 {to_account}")
# 检查转账金额是否合法
if amount <= 0:
# 金额不合法,抛出异常
raise ValueError("转账金额必须大于0")
# 检查源账户是否合法
if from_account == "invalid_account":
# 源账户不存在,抛出异常
raise ValueError("源账户不存在")
# 从源账户扣除金额
db.update_account_balance(from_account, -amount)
# 向目标账户增加金额
db.update_account_balance(to_account, amount)
# 打印转账成功的信息
print("转账成功")
# 返回True表示转账成功
return True
# 测试正常转账操作
try:
# 账户1向账户2转账100元
result = transfer_money("account1", "account2", 100)
# 打印转账结果
print(f"转账结果: {result}")
except Exception as e:
# 捕获异常并打印失败信息
print(f"转账失败: {e}")
# 打印分隔信息,准备测试异常情况
print("\n=== 测试异常情况 ===")
try:
# 调用转账函数,使用无效账户
result = transfer_money("invalid_account", "account2", 100)
# 打印转账结果
print(f"转账结果: {result}")
except Exception as e:
# 捕获异常并打印失败信息
print(f"转账失败: {e}")
# 打印分隔信息,准备测试转账负金额
print("\n=== 测试负数金额 ===")
try:
# 调用转账函数,尝试转账负数金额
result = transfer_money("account1", "account2", -50)
# 打印转账结果
print(f"转账结果: {result}")
except Exception as e:
# 捕获异常并打印失败信息
print(f"转账失败: {e}")
6.6 参数验证装饰器 #
# 从functools模块导入wraps,用于保留原函数的元数据
from functools import wraps
# 导入inspect模块,用于获取函数签名
import inspect
# 定义参数验证装饰器,接收多个验证器作为关键字参数
def validate_params(**validators):
# 定义装饰器函数,接收被装饰的函数func
def decorator(func):
# 使用wraps来保留原函数元数据信息
@wraps(func)
# 定义包装函数,接收任意位置参数和关键字参数
def wrapper(*args, **kwargs):
# 获取被装饰函数的签名对象
sig = inspect.signature(func)
# 绑定传入的参数到函数签名
bound_args = sig.bind(*args, **kwargs)
# 应用参数的默认值
bound_args.apply_defaults()
# 遍历所有需要验证的参数及其验证器
for param_name, validator in validators.items():
# 判断参数名是否在绑定参数中
if param_name in bound_args.arguments:
# 获取参数名称对应的值
value = bound_args.arguments[param_name]
# 执行验证器检查参数值是否合法
if not validator(value):
# 如果不通过,抛出 ValueError 异常,说明是哪个参数验证失败
raise ValueError(f"参数 {param_name} 验证失败: {value}")
# 所有参数验证通过后,调用被装饰函数
return func(*args, **kwargs)
# 返回包装的函数
return wrapper
# 返回装饰器函数
return decorator
# 定义判断值是否为正数的函数
def is_positive(value):
# 判断值是否为 int 或 float 类型且大于 0
return isinstance(value, (int, float)) and value > 0
# 定义判断值是否为字符串的函数
def is_string(value):
# 判断值是否为字符串类型
return isinstance(value, str)
# 定义判断值是否为合法邮箱的函数
def is_email(value):
# 判断值是否为字符串且包含 @ 和 .
return isinstance(value, str) and '@' in value and '.' in value
# 使用参数验证装饰器,对 create_user 函数的参数进行验证
@validate_params(
# name 参数必须为字符串
name=is_string,
# age 参数必须为 0 到 150 之间的整数
age=lambda x: isinstance(x, int) and 0 < x < 150,
# email 参数必须为合法邮箱
email=is_email,
# salary 参数必须为正数
salary=is_positive
)
# 定义创建用户的函数
def create_user(name, age, email, salary):
# 返回包含用户信息的字典
return {
'name': name,
'age': age,
'email': email,
'salary': salary
}
# 测试传入有效参数的情况
try:
# 创建一个参数均合法的用户
user = create_user("张三", 25, "zhangsan@example.com", 5000)
# 打印用户创建成功的信息
print(f"用户创建成功: {user}")
# 捕获参数验证失败时抛出的异常
except ValueError as e:
# 打印参数验证失败的原因
print(f"参数验证失败: {e}")
# 测试传入无效参数的情况
try:
# 创建用户,参数部分不合法
user = create_user("", 25, "invalid-email", -1000)
# 打印用户创建成功的信息
print(f"用户创建成功: {user}")
# 捕获参数验证失败时抛出的异常
except ValueError as e:
# 打印参数验证失败的原因
print(f"参数验证失败: {e}")6.7 重试机制装饰器 #
# 导入time模块,用于实现延迟
import time
# 从functools模块导入wraps,用于保持装饰器包装后函数的元数据
from functools import wraps
# 导入random模块,用于生成随机数
import random
# 定义一个重试机制装饰器
def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
# 定义装饰器内部函数,接受被装饰的函数func
def decorator(func):
# 用wraps装饰器保持原函数元数据
@wraps(func)
# 定义包装函数,接收任意参数
def wrapper(*args, **kwargs):
# 初始化重试延迟时间
current_delay = delay
# 初始化最后一个异常
last_exception = None
# 循环尝试执行函数,最多max_attempts次
for attempt in range(max_attempts):
try:
# 尝试调用原始函数
return func(*args, **kwargs)
# 捕获指定的异常
except exceptions as e:
# 记录最后一次发生的异常
last_exception = e
# 如果已经到达最后一次尝试
if attempt == max_attempts - 1:
# 打印失败信息
print(f"函数 {func.__name__} 在 {max_attempts} 次尝试后仍然失败")
# 抛出最后一次异常
raise last_exception
# 打印当前重试失败的信息
print(f"函数 {func.__name__} 第 {attempt + 1} 次尝试失败: {str(e)}")
# 打印等待时间信息
print(f"等待 {current_delay} 秒后重试...")
# 等待指定的时间
time.sleep(current_delay)
# 延迟时间按指数增长(指数退避)
current_delay *= backoff
# 如果所有尝试都失败,抛出最后一次异常
raise last_exception
# 返回包装后的函数
return wrapper
# 返回装饰器
return decorator
# 应用重试机制装饰器到unreliable_function函数
@retry(max_attempts=3, delay=1, backoff=2, exceptions=(ValueError, ConnectionError))
# 定义一个不稳定的函数,模拟失败和成功
def unreliable_function():
# 以30%的概率成功,70%概率失败
if random.random() < 0.3:
# 打印成功信息
print("函数执行成功!")
# 返回成功结果
return "成功结果"
else:
# 抛出连接异常,模拟网络问题
raise ConnectionError("网络连接失败")
# 主程序入口:测试重试机制
try:
# 调用有重试机制的不稳定函数,并获取结果
result = unreliable_function()
# 打印最终结果
print(f"最终结果: {result}")
# 捕获所有异常
except Exception as e:
# 打印所有尝试都失败的信息
print(f"所有重试都失败了: {e}")6.8 监控告警装饰器 #
# 导入time模块,用于计时
import time
# 从functools模块导入wraps函数,用于保持被装饰函数的元信息
from functools import wraps
# 从datetime模块导入datetime类(虽然此处未直接使用)
from datetime import datetime
# 导入logging模块,用于日志记录
import logging
# 创建名为'monitoring'的日志记录器
monitoring_logger = logging.getLogger('monitoring')
# 设置日志级别为INFO
monitoring_logger.setLevel(logging.INFO)
# 创建控制台流式日志处理器
handler = logging.StreamHandler()
# 定义日志输出格式:时间-级别-消息体
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 设置处理器的日志格式
handler.setFormatter(formatter)
# 将处理器添加到日志记录器
monitoring_logger.addHandler(handler)
# 定义监控函数性能的装饰器
def monitor_performance(threshold_seconds=1.0, alert_on_exception=True):
# 装饰器实际实现部分,接收被装饰函数
def decorator(func):
# 保持函数元信息,定义包装函数
@wraps(func)
def wrapper(*args, **kwargs):
# 记录函数开始执行的时间戳
start_time = time.time()
# 获取被装饰函数的名字
func_name = func.__name__
try:
# 调用原始函数并获取返回结果
result = func(*args, **kwargs)
# 计算函数实际执行时间
execution_time = time.time() - start_time
# 判断执行时间是否超过阈值
if execution_time > threshold_seconds:
# 若超过阈值,记录性能告警日志
alert_msg = f"性能告警: 函数 {func_name} 执行时间 {execution_time:.2f}s 超过阈值 {threshold_seconds}s"
monitoring_logger.warning(alert_msg)
else:
# 未超过阈值,正常记录执行成功的日志
monitoring_logger.info(f"函数 {func_name} 执行成功,耗时 {execution_time:.2f}s")
# 返回被装饰函数的返回结果
return result
except Exception as e:
# 若出现异常,计算已消耗的时间
execution_time = time.time() - start_time
# 如果需要在异常时告警
if alert_on_exception:
# 记录异常告警日志
alert_msg = f"异常告警: 函数 {func_name} 执行失败,异常: {str(e)},耗时 {execution_time:.2f}s"
monitoring_logger.error(alert_msg)
else:
# 仅记录异常信息的普通日志
monitoring_logger.info(f"函数 {func_name} 执行失败,异常: {str(e)},耗时 {execution_time:.2f}s")
# 重新抛出异常以便外部捕获
raise
# 返回包装后的函数
return wrapper
# 返回装饰器
return decorator
# 使用性能监控装饰器,阈值为0.5秒,异常时告警
@monitor_performance(threshold_seconds=0.5, alert_on_exception=True)
def fast_function():
# 快速函数:休眠0.1秒
time.sleep(0.1)
# 返回完成信息
return "快速执行完成"
# 使用性能监控装饰器,阈值为0.5秒,异常时告警
@monitor_performance(threshold_seconds=0.5, alert_on_exception=True)
def slow_function():
# 慢速函数:休眠1.0秒
time.sleep(1.0)
# 返回完成信息
return "慢速执行完成"
# 使用性能监控装饰器,阈值为0.5秒,异常时告警
@monitor_performance(threshold_seconds=0.5, alert_on_exception=True)
def error_function():
# 该函数会主动抛出异常
raise ValueError("这是一个测试异常")
# 打印分割线,测试快速函数
print("=== 测试快速函数 ===")
# 调用快速函数,获取返回结果
result = fast_function()
# 打印返回的结果
print(f"结果: {result}")
# 打印分割线,测试慢速函数
print("\n=== 测试慢速函数 ===")
# 调用慢速函数,获取返回结果
result = slow_function()
# 打印返回的结果
print(f"结果: {result}")
# 打印分割线,测试异常函数
print("\n=== 测试异常函数 ===")
try:
# 调用抛出异常的函数,并尝试获取返回结果
result = error_function()
# 打印返回的结果(如果调用未抛出异常)
print(f"结果: {result}")
except Exception as e:
# 捕获异常,并打印异常信息
print(f"捕获异常: {e}")7. 注意事项 #
- 使用functools.wraps:保持被装饰函数的元数据
- 参数传递:使用
*args, **kwargs确保参数正确传递 - 返回值处理:确保装饰器正确返回原函数的结果
- 装饰器顺序:多个装饰器的应用顺序很重要
- 性能考虑:装饰器会增加函数调用开销
- 调试困难:过度使用装饰器可能使代码难以调试
8. inspect #
inspect 模块可以帮助我们在装饰器内部获知被装饰函数的参数信息,实现更加灵活和自动化的功能,典型场景是通用参数校验装饰器。
inspect.signature(func) - 获取函数签名,函数签名包含了函数的参数信息:参数名、参数类型、默认值、注解等。
8.1 inspect.signature(func) - 获取函数签名 #
什么是函数签名? 函数签名包含了函数的参数信息:参数名、参数类型、默认值、注解等。
# 导入inspect模块,用于获取函数签名
import inspect
# 定义一个示例函数,包含位置参数、默认参数、可变参数、关键字参数和可变关键字参数
def example_func(name, age=25, *args, email=None, **kwargs):
pass
# 获取example_func的函数签名对象
sig = inspect.signature(example_func)
print(sig) # 输出: (name, age=25, *args, email=None, **kwargs)
print(type(sig)) # 输出:<class 'inspect.Signature'>
8.2 签名的结构 #
# 导入inspect模块,用于获取函数签名
import inspect
# 定义一个示例函数,包含位置参数、默认参数、可变参数、关键字参数和可变关键字参数
def example_func(name, age=25, *args, email=None, **kwargs):
pass
# 获取example_func的函数签名对象
sig = inspect.signature(example_func)
print(sig) # 输出: (name, age=25, *args, email=None, **kwargs)
print(type(sig)) # 输出:<class 'inspect.Signature'>
# 参数类型
# POSITIONAL_ONLY 位置参数
# POSITIONAL_OR_KEYWORD 位置或关键字参数
# VAR_POSITIONAL 可变位置参数
# KEYWORD_ONLY 关键字参数
# VAR_KEYWORD 可变关键字参数
def analyze_signature(func):
sig = inspect.signature(func)
print(f"函数 {func.__name__} 的签名: {sig}")
# 查看参数详情
for param_name, param in sig.parameters.items():
print(f" 参数: {param_name}") # 参数名
print(f" 类型: {param.kind}") # 参数类型
print(f" 默认值: {param.default}") # 默认值
print(f" 注解: {param.annotation}") # 注解
analyze_signature(example_func)输出:
(name, age=25, *args, email=None, **kwargs)
<class 'inspect.Signature'>
函数 example_func 的签名: (name, age=25, *args, email=None, **kwargs)
参数: name
类型: POSITIONAL_OR_KEYWORD
默认值: <class 'inspect._empty'>
注解: <class 'inspect._empty'>
参数: age
类型: POSITIONAL_OR_KEYWORD
默认值: 25
注解: <class 'inspect._empty'>
参数: args
类型: VAR_POSITIONAL
默认值: <class 'inspect._empty'>
注解: <class 'inspect._empty'>
参数: email
类型: KEYWORD_ONLY
默认值: None
注解: <class 'inspect._empty'>
参数: kwargs
类型: VAR_KEYWORD
默认值: <class 'inspect._empty'>
注解: <class 'inspect._empty'>8.3 sig.bind(*args, **kwargs) - 参数绑定 #
作用:将实际传入的参数按照函数签名进行绑定,验证参数是否合法。
# 导入inspect模块,用于获取函数签名
import inspect
# 定义一个带有默认参数的简单函数
def func(a, b, c=10):
return a + b + c
# 获取func函数的签名对象
sig = inspect.signature(func)
# 正确绑定参数的示例
try:
# 绑定位置参数a=1, b=2,c使用默认值
bound_args = sig.bind(1, 2)
# 打印绑定成功后的参数字典
print("绑定成功:", bound_args.arguments)
except TypeError as e:
# 如果绑定失败,打印错误信息
print("绑定失败:", e)
# 错误绑定示例 - 缺少必需参数b
try:
# 只传递一个参数a=1,缺少b
bound_args = sig.bind(1)
# 如果绑定成功,打印参数
print("绑定成功:", bound_args.arguments)
except TypeError as e:
# 绑定失败时打印错误信息
print("绑定失败:", e)
# 使用关键字参数进行绑定
try:
# 通过关键字参数绑定a=1, b=2,c使用默认值
bound_args = sig.bind(a=1, b=2)
# 打印关键字绑定成功后的参数字典
print("关键字绑定成功:", bound_args.arguments)
except TypeError as e:
# 关键字绑定失败时打印错误信息
print("关键字绑定失败:", e)输出:
绑定成功: {'a': 1, 'b': 2}
绑定失败: missing a required argument: 'b'
关键字绑定成功: {'a': 1, 'b': 2}8.4 BoundArguments 对象 #
sig.bind() 返回一个 BoundArguments 对象,包含:
# 导入inspect模块,用于获取函数签名相关功能
import inspect
# 定义一个带有默认参数的简单函数
def func(x, y=10, z=20):
pass
# 获取func函数的签名对象
sig = inspect.signature(func)
# 绑定参数:x=5,z=30,y使用默认值
bound_args = sig.bind(5, z=30)
# 打印bound_args的类型
print("bound_args 类型:", type(bound_args))
# 打印当前绑定的参数(有序字典)
print("bound_args.arguments:", bound_args.arguments)
# 打印位置参数组成的元组
print("bound_args.args:", bound_args.args)
# 打印关键字参数组成的字典
print("bound_args.kwargs:", bound_args.kwargs)
# 打印与BoundArguments对象关联的签名
print("关联的签名:", bound_args.signature)输出:
bound_args 类型: <class 'inspect.BoundArguments'>
bound_args.arguments: {'x': 5, 'z': 30}
bound_args.args: (5,)
bound_args.kwargs: {'z': 30}
关联的签名: (x, y=10, z=20)8.5. bound_args.apply_defaults() - 应用默认值 #
作用: 为没有传入值的参数填充默认值。
# 导入inspect模块,用于获取函数签名和参数绑定
import inspect
# 定义一个带有默认参数的函数
def func(a, b=100, c=200, d=300):
# 返回格式化的参数字符串
return f"a={a}, b={b}, c={c}, d={d}"
# 获取func函数的签名对象
sig = inspect.signature(func)
# 只传入部分参数,a=1, c=999,b和d未传递
bound_args = sig.bind(1, c=999) # a=1, c=999, b和d使用默认值
# 打印应用默认值前的参数绑定情况
print("应用默认值前:", bound_args.arguments)
# 应用参数的默认值,补全未传递的参数
bound_args.apply_defaults()
# 打印应用默认值后的参数绑定情况
print("应用默认值后:", bound_args.arguments)
# 使用绑定参数完整调用原函数
print("完整调用:", func(*bound_args.args, **bound_args.kwargs))输出:
应用默认值前: {'a': 1, 'c': 999}
应用默认值后: {'a': 1, 'b': 100, 'c': 999, 'd': 300}
完整调用: a=1, b=100, c=999, d=3008.6 annotations #
__annotations__ 是 Python 函数和方法的一个属性,用于存储参数和返回值的类型提示信息。它的本质是一个字典,键为参数名(或 "return" 表示返回值),值为标注的类型对象。
8.6.1 基本说明 #
- 如果函数有类型注解,
func.__annotations__会返回所有参数和返回值的注解; - 如果没有注解,则返回的是一个空字典
{}; - 注解只是一种提示/文档机制,Python 不会自动强制类型校验(除非用第三方工具或自定义装饰器)。
8.6.2 示例 #
def add(x: int, y: int) -> int:
return x + y
print(add.__annotations__)
# 输出: {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}你可以通过遍历 __annotations__ 来获取所有注解:
def foo(a: int, b: str = "default", c: float = 3.14) -> bool:
pass
for name, t in foo.__annotations__.items():
print(f"{name}: {t}")
# 输出:
# a: <class 'int'>
# b: <class 'str'>
# c: <class 'float'>
# return: <class 'bool'>8.6.3 在装饰器中的应用 #
装饰器可以通过读取 func.__annotations__,结合 inspect 等模块实现自动的参数类型检查、文档自动生成等高级功能。例如上一节的 type_assert 装饰器,就是使用注解做类型校验的实践案例。
⚠️ 注意:类型注解只是用于类型提示和文档说明,不具备强制执行作用,但能极大提升代码可读性和开发效率。
8.7 自动参数检查 #
下面演示如何结合inspect.signature动态地获取函数参数,实现自动参数检查。
假设我们希望有一个装饰器,可以根据类型注解自动校验传入的实参类型是否正确。如果类型不符,则抛出异常。
# 导入inspect模块用于获取函数签名
import inspect
# 从functools模块导入wraps用于装饰器
from functools import wraps
# 定义类型断言装饰器
def type_assert(func):
# 获取func的函数签名信息
sig = inspect.signature(func)
# 定义包装器函数,保持原函数元信息
@wraps(func)
def wrapper(*args, **kwargs):
# 绑定传入参数到签名上
bound_args = sig.bind(*args, **kwargs)
# 应用参数的默认值
bound_args.apply_defaults()
# 遍历所有绑定参数进行类型检查
for name, value in bound_args.arguments.items():
# 检查该参数名是否有类型注解
if name in func.__annotations__:
# 获取该参数的类型注解
expected_type = func.__annotations__[name]
# 如果参数实际类型与期望类型不符
if not isinstance(value, expected_type):
# 抛出类型异常并带有详细说明
raise TypeError(f"参数 {name} 期望类型 {expected_type.__name__},但收到值 {value!r} (类型: {type(value).__name__})")
# 参数类型通过检查后,调用原始函数
return func(*args, **kwargs)
# 返回包装器函数
return wrapper
# 使用type_assert装饰器修饰greet函数
@type_assert
def greet(name: str, age: int):
# 打印问候语,包含名字和年龄
print(f"大家好,我叫{name},今年{age}岁。")
# 调用greet函数,参数类型正确,正常执行
greet("张三", 25)
# 调用greet函数,age参数类型错误,将抛出TypeError异常
greet("李四", "二十")输出示例:
大家好,我叫张三,今年25岁。
Traceback (most recent call last):
...
TypeError: 参数 age 期望类型 int,但收到值 '二十' (类型: str)9.参考回答 #
9.1 什么是装饰器(开门见山) #
装饰器是 Python 中的一种设计模式,用于在不修改原函数代码的前提下增强或修改函数功能。本质上是一个接受函数并返回新函数的高阶函数。
9.2 核心工作原理(展现理解深度) #
- 装饰器函数接收一个函数作为参数
- 在内部定义一个包装函数,在调用原函数前后添加逻辑
- 返回包装后的新函数
- 使用
@语法糖,让代码更简洁
9.3 主要类型(展示知识广度) #
- 函数装饰器:装饰普通函数
- 带参数的装饰器:需要先调用外层函数返回装饰器
- 类装饰器:用类实现,通常实现
__call__方法 - 装饰类:装饰类本身,常用于单例模式等功能注入
9.4 实际应用场景(体现实战经验) #
- 性能测试:统计函数执行时间
- 日志记录:记录函数调用和返回值
- 缓存机制:缓存函数结果,提升性能
- 权限验证:在函数执行前进行权限检查
- 参数验证:校验传入参数是否合法
- 重试机制:函数失败后自动重试
- 事务处理:在数据库操作中自动管理事务
9.5 重要注意事项(展现专业素养) #
- 使用
functools.wraps:保持被装饰函数的元数据,避免函数名、文档等丢失 - 参数传递:使用
*args和**kwargs确保参数正确传递 - 返回值处理:确保装饰器正确返回原函数的结果
- 装饰器顺序:多个装饰器的应用顺序很重要,从下往上执行
- 调试难度:过度使用装饰器可能增加调试复杂度
9.6 加分项(如果时间允许) #
提到 inspect 模块,说明装饰器可以通过函数签名获取参数信息,实现更自动化的功能,比如基于类型注解的自动参数校验。
9.7 回答技巧 #
- 先说概念,再说原理,最后举例应用
- 用“比如”“例如”连接,自然引入场景
- 结束时可以说“这是我理解的核心点,有需要我可以展开”