Python装饰器(decorators)详解
装饰器(Decorator)是Python中一种强大而灵活的语法特性,它允许我们在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。本文将详细介绍Python装饰器的概念、原理、用法和最佳实践。
一、装饰器概述
1. 什么是装饰器?
装饰器是一个接受函数作为参数,并返回一个新函数的函数或类。它允许我们在不修改原有函数代码的情况下,为函数添加额外的功能,如日志记录、性能分析、权限验证等。
2. 装饰器的作用
装饰器的主要作用是:
- 扩展功能:在不修改原有函数代码的情况下,为函数添加额外的功能
- 代码复用:将通用功能抽取出来,通过装饰器的方式复用
- 解耦:将功能实现与功能扩展分离
- 简洁代码:使用@符号语法,使代码更加简洁易读
- 提高可维护性:便于统一管理和修改功能扩展
3. 装饰器的特点
装饰器具有以下特点:
- 高阶函数:接受函数作为参数,并返回一个新函数
- 闭包:可以访问外部函数的变量
- 语法糖:使用@符号语法,简化装饰器的使用
- 透明性:装饰后的函数看起来和原函数一样
- 可组合性:多个装饰器可以组合使用
4. 装饰器的应用场景
装饰器适用于以下场景:
- 日志记录:记录函数的调用情况
- 性能分析:计算函数的执行时间
- 权限验证:检查用户是否有权限执行函数
- 缓存:缓存函数的返回结果
- 重试机制:在函数执行失败时自动重试
- 异常处理:捕获和处理函数执行过程中的异常
- 事务管理:管理数据库事务
- 参数验证:验证函数参数的有效性
二、装饰器的基本原理
1. 函数对象
在Python中,函数是一等公民(First-class Citizen),具有以下特点:
- 可以作为参数传递给其他函数
- 可以作为返回值从函数中返回
- 可以赋值给变量
- 可以存储在数据结构中
- 可以动态创建
# 函数对象示例
def greet(name):
"""向指定名称的人打招呼"""
return f"Hello, {name}!"
# 将函数赋值给变量
hello = greet
print(hello("张三")) # 输出:Hello, 张三!
# 将函数作为参数传递给其他函数
def apply_function(func, value):
"""将函数应用于值"""
return func(value)
result = apply_function(greet, "李四")
print(result) # 输出:Hello, 李四!
# 将函数作为返回值从函数中返回
def make_greeter(prefix):
"""创建一个带有前缀的问候函数"""
def greeter(name):
"""带有前缀的问候函数"""
return f"{prefix}, {name}!"
return greeter
morning_greeter = make_greeter("Good morning")
print(morning_greeter("王五")) # 输出:Good morning, 王五!
2. 闭包
闭包是指一个函数可以访问并操作其外部作用域中的变量,即使外部函数已经执行完毕。闭包由函数和其环境变量组成。
# 闭包示例
def outer_function(message):
"""外部函数"""
msg = message
def inner_function():
"""内部函数(闭包)"""
print(msg)
return inner_function
# 创建闭包
my_func = outer_function("Hello, World!")
# 调用闭包
my_func() # 输出:Hello, World!
3. 高阶函数
高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。
# 高阶函数示例
def add(a, b):
"""计算两个数的和"""
return a + b
def multiply(a, b):
"""计算两个数的乘积"""
return a * b
def apply_operation(operation, a, b):
"""将操作应用于两个数"""
return operation(a, b)
# 使用高阶函数
result1 = apply_operation(add, 3, 5)
print(result1) # 输出:8
result2 = apply_operation(multiply, 3, 5)
print(result2) # 输出:15
三、装饰器的实现方式
1. 函数装饰器
函数装饰器是最常见的装饰器类型,它是一个接受函数作为参数,并返回一个新函数的函数。
# 函数装饰器示例
def my_decorator(func):
"""一个简单的装饰器"""
def wrapper(*args, **kwargs):
"""包装函数"""
print("装饰器开始执行")
result = func(*args, **kwargs)
print("装饰器结束执行")
return result
return wrapper
# 使用装饰器
@my_decorator
def greet(name):
"""打印问候信息"""
print(f"Hello, {name}!")
return f"Greeted {name}"
# 调用装饰后的函数
result = greet("张三")
print(f"函数返回值: {result}")
# 输出:
# 装饰器开始执行
# Hello, 张三!
# 装饰器结束执行
# 函数返回值: Greeted 张三
2. 类装饰器
类装饰器是一个接受函数作为参数,并返回一个新函数的类。类装饰器需要实现__call__方法,该方法会在装饰后的函数被调用时执行。
# 类装饰器示例
class MyDecorator:
"""一个简单的类装饰器"""
def __init__(self, func):
"""初始化装饰器"""
self.func = func
def __call__(self, *args, **kwargs):
"""调用装饰后的函数"""
print("类装饰器开始执行")
result = self.func(*args, **kwargs)
print("类装饰器结束执行")
return result
# 使用类装饰器
@MyDecorator
def greet(name):
"""打印问候信息"""
print(f"Hello, {name}!")
return f"Greeted {name}"
# 调用装饰后的函数
result = greet("张三")
print(f"函数返回值: {result}")
# 输出:
# 类装饰器开始执行
# Hello, 张三!
# 类装饰器结束执行
# 函数返回值: Greeted 张三
四、装饰器的基本用法
1. 简单装饰器
简单装饰器是指不接受参数的装饰器,它只对函数进行简单的包装。
# 简单装饰器示例
def logger(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
@logger
def add(a, b):
"""计算两个数的和"""
return a + b
@logger
def greet(name, greeting="Hello"):
"""向指定名称的人打招呼"""
return f"{greeting}, {name}!"
# 调用装饰后的函数
result1 = add(3, 5)
print(f"结果: {result1}")
result2 = greet("张三", greeting="你好")
print(f"结果: {result2}")
2. 带参数的装饰器
带参数的装饰器是指接受参数的装饰器,它需要在装饰器函数外部再包装一层函数。
# 带参数的装饰器示例
def repeat(times):
"""重复执行函数指定次数的装饰器"""
def decorator(func):
"""装饰器函数"""
def wrapper(*args, **kwargs):
"""包装函数"""
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
# 使用带参数的装饰器
@repeat(3)
def greet(name):
"""打印问候信息"""
print(f"Hello, {name}!")
return f"Greeted {name}"
# 调用装饰后的函数
result = greet("张三")
print(f"函数返回值: {result}")
# 输出:
# Hello, 张三!
# Hello, 张三!
# Hello, 张三!
# 函数返回值: ['Greeted 张三', 'Greeted 张三', 'Greeted 张三']
3. 多个装饰器
多个装饰器可以同时应用于一个函数,装饰器的执行顺序是从下到上。
# 多个装饰器示例
def decorator1(func):
"""装饰器1"""
def wrapper(*args, **kwargs):
"""包装函数"""
print("装饰器1开始执行")
result = func(*args, **kwargs)
print("装饰器1结束执行")
return result
return wrapper
def decorator2(func):
"""装饰器2"""
def wrapper(*args, **kwargs):
"""包装函数"""
print("装饰器2开始执行")
result = func(*args, **kwargs)
print("装饰器2结束执行")
return result
return wrapper
# 使用多个装饰器,执行顺序是从下到上
@decorator1
@decorator2
def greet(name):
"""打印问候信息"""
print(f"Hello, {name}!")
return f"Greeted {name}"
# 调用装饰后的函数
result = greet("张三")
print(f"函数返回值: {result}")
# 输出:
# 装饰器1开始执行
# 装饰器2开始执行
# Hello, 张三!
# 装饰器2结束执行
# 装饰器1结束执行
# 函数返回值: Greeted 张三
4. 装饰器链
装饰器链是指多个装饰器组合使用,形成一个装饰器链。
# 装饰器链示例
def log_entry(func):
"""记录函数开始执行的装饰器"""
def wrapper(*args, **kwargs):
"""包装函数"""
print(f"{func.__name__}开始执行")
return func(*args, **kwargs)
return wrapper
def log_exit(func):
"""记录函数结束执行的装饰器"""
def wrapper(*args, **kwargs):
"""包装函数"""
result = func(*args, **kwargs)
print(f"{func.__name__}结束执行")
return result
return wrapper
def performance(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
# 使用装饰器链
@log_entry
@performance
@log_exit
def slow_function():
"""模拟耗时操作"""
import time
time.sleep(1)
return "完成"
# 调用装饰后的函数
result = slow_function()
print(f"函数返回值: {result}")
# 输出:
# slow_function开始执行
# slow_function结束执行
# slow_function执行时间: 1.0001秒
# 函数返回值: 完成
五、内置装饰器
Python提供了一些内置的装饰器,用于实现常见的功能。
1. @staticmethod
@staticmethod装饰器用于定义静态方法,静态方法不需要访问类或实例的属性,可以通过类名直接调用。
# @staticmethod示例
class Math:
"""数学工具类"""
@staticmethod
def add(a, b):
"""计算两个数的和"""
return a + b
@staticmethod
def multiply(a, b):
"""计算两个数的乘积"""
return a * b
# 调用静态方法
result1 = Math.add(3, 5)
print(f"3 + 5 = {result1}") # 输出:3 + 5 = 8
result2 = Math.multiply(3, 5)
print(f"3 × 5 = {result2}") # 输出:3 × 5 = 15
2. @classmethod
@classmethod装饰器用于定义类方法,类方法可以访问类属性,但不能访问实例属性,可以通过类名或实例调用。
# @classmethod示例
class Person:
"""人类"""
species = "人类" # 类属性
def __init__(self, name, age):
"""初始化实例属性"""
self.name = name
self.age = age
@classmethod
def get_species(cls):
"""获取物种信息"""
return cls.species
@classmethod
def create_adult(cls, name):
"""创建一个成年人类实例"""
return cls(name, 18)
# 调用类方法
print(f"物种: {Person.get_species()}") # 输出:物种: 人类
# 使用类方法创建实例
adult = Person.create_adult("张三")
print(f"姓名: {adult.name}, 年龄: {adult.age}") # 输出:姓名: 张三, 年龄: 18
3. @property
@property装饰器用于定义属性访问器,将方法转换为属性,可以通过属性语法访问方法的返回值。
# @property示例
class Circle:
"""圆形类"""
def __init__(self, radius):
"""初始化半径"""
self._radius = radius
@property
def radius(self):
"""获取半径"""
return self._radius
@radius.setter
def radius(self, value):
"""设置半径"""
if value <= 0:
raise ValueError("半径必须大于0")
self._radius = value
@property
def area(self):
"""获取面积"""
import math
return math.pi * self._radius ** 2
@property
def circumference(self):
"""获取周长"""
import math
return 2 * math.pi * self._radius
# 使用property
circle = Circle(5)
print(f"半径: {circle.radius}") # 输出:半径: 5
print(f"面积: {circle.area}") # 输出:面积: 78.53981633974483
print(f"周长: {circle.circumference}") # 输出:周长: 31.41592653589793
# 设置半径
circle.radius = 10
print(f"新半径: {circle.radius}") # 输出:新半径: 10
print(f"新面积: {circle.area}") # 输出:新面积: 314.1592653589793
# 尝试设置无效半径
try:
circle.radius = -5
except ValueError as e:
print(f"错误: {e}") # 输出:错误: 半径必须大于0
4. @functools.wraps
@functools.wraps装饰器用于保留原函数的元信息,如函数名、文档字符串、参数列表等。
# @functools.wraps示例
import functools
def my_decorator(func):
"""一个简单的装饰器"""
@functools.wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
"""包装函数"""
print("装饰器开始执行")
result = func(*args, **kwargs)
print("装饰器结束执行")
return result
return wrapper
@my_decorator
def greet(name):
"""向指定名称的人打招呼"""
return f"Hello, {name}!"
# 查看函数名
print(f"函数名: {greet.__name__}") # 输出:函数名: greet
# 查看文档字符串
print(f"文档字符串: {greet.__doc__}") # 输出:文档字符串: 向指定名称的人打招呼
# 查看参数列表
import inspect
print(f"参数列表: {inspect.signature(greet)}") # 输出:参数列表: (name)
5. @functools.lru_cache
@functools.lru_cache装饰器用于缓存函数的返回结果,避免重复计算,提高性能。
# @functools.lru_cache示例
import functools
import time
def fibonacci(n):
"""计算斐波那契数列的第n项"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
@functools.lru_cache(maxsize=None)
def fibonacci_cached(n):
"""计算斐波那契数列的第n项(带缓存)"""
if n <= 1:
return n
return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
# 测试性能
n = 35
start_time = time.time()
result1 = fibonacci(n)
end_time = time.time()
print(f"未缓存的斐波那契数列(n={n}): {result1}")
print(f"耗时: {end_time - start_time:.4f}秒")
start_time = time.time()
result2 = fibonacci_cached(n)
end_time = time.time()
print(f"带缓存的斐波那契数列(n={n}): {result2}")
print(f"耗时: {end_time - start_time:.4f}秒")
# 查看缓存信息
print(f"缓存信息: {fibonacci_cached.cache_info()}")
6. @functools.singledispatch
@functools.singledispatch装饰器用于实现函数的单分派泛型功能,根据第一个参数的类型调用不同的函数实现。
# @functools.singledispatch示例
from functools import singledispatch
@singledispatch
def process_data(data):
"""处理数据的通用函数"""
raise NotImplementedError(f"不支持的数据类型: {type(data).__name__}")
@process_data.register(int)
def _(data):
"""处理整数类型"""
print(f"处理整数: {data}")
return data * 2
@process_data.register(str)
def _(data):
"""处理字符串类型"""
print(f"处理字符串: {data}")
return data.upper()
@process_data.register(list)
def _(data):
"""处理列表类型"""
print(f"处理列表: {data}")
return [x * 2 for x in data]
@process_data.register(dict)
def _(data):
"""处理字典类型"""
print(f"处理字典: {data}")
return {k: v * 2 for k, v in data.items()}
# 测试不同类型的数据
result1 = process_data(5)
print(f"结果: {result1}")
result2 = process_data("hello")
print(f"结果: {result2}")
result3 = process_data([1, 2, 3])
print(f"结果: {result3}")
result4 = process_data({"a": 1, "b": 2})
print(f"结果: {result4}")
# 测试不支持的数据类型
try:
process_data(3.14)
except NotImplementedError as e:
print(f"错误: {e}")
六、装饰器的高级用法
1. 装饰器工厂
装饰器工厂是指返回装饰器的函数,它可以根据参数动态创建装饰器。
# 装饰器工厂示例
import functools
def create_decorator(prefix):
"""创建带有前缀的装饰器"""
def decorator(func):
"""装饰器函数"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
print(f"{prefix}: 开始执行函数 {func.__name__}")
result = func(*args, **kwargs)
print(f"{prefix}: 结束执行函数 {func.__name__}")
return result
return wrapper
return decorator
# 创建两个不同的装饰器
debug_decorator = create_decorator("DEBUG")
info_decorator = create_decorator("INFO")
# 使用不同的装饰器
@debug_decorator
def debug_function():
"""调试函数"""
print("这是一个调试函数")
return "调试完成"
@info_decorator
def info_function():
"""信息函数"""
print("这是一个信息函数")
return "信息完成"
# 调用装饰后的函数
result1 = debug_function()
print(f"结果: {result1}")
result2 = info_function()
print(f"结果: {result2}")
2. 参数化装饰器
参数化装饰器是指可以接受参数的装饰器,它需要在装饰器函数外部再包装一层函数。
# 参数化装饰器示例
import functools
import time
def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
"""重试装饰器"""
def decorator(func):
"""装饰器函数"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except exceptions as e:
attempts += 1
if attempts == max_attempts:
raise
print(f"尝试失败 ({attempts}/{max_attempts}): {e}")
time.sleep(delay)
return wrapper
return decorator
# 使用参数化装饰器
@retry(max_attempts=5, delay=0.5, exceptions=(ValueError,))
def risky_function():
"""模拟有风险的操作"""
import random
if random.random() < 0.7:
raise ValueError("操作失败")
return "操作成功"
# 调用装饰后的函数
try:
result = risky_function()
print(f"结果: {result}")
except ValueError as e:
print(f"最终失败: {e}")
3. 类装饰器
类装饰器是指使用类来实现装饰器,需要实现__call__方法。
# 类装饰器示例
import functools
class Timer:
"""计算函数执行时间的类装饰器"""
def __init__(self, func):
"""初始化装饰器"""
self.func = func
self.total_time = 0
self.call_count = 0
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
"""调用装饰后的函数"""
import time
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
elapsed_time = end_time - start_time
self.total_time += elapsed_time
self.call_count += 1
print(f"{self.func.__name__}执行时间: {elapsed_time:.4f}秒")
print(f"总执行时间: {self.total_time:.4f}秒, 调用次数: {self.call_count}")
return result
# 使用类装饰器
@Timer
def slow_function():
"""模拟耗时操作"""
import time
time.sleep(1)
return "完成"
# 调用装饰后的函数
result1 = slow_function()
print(f"结果: {result1}")
result2 = slow_function()
print(f"结果: {result2}")
4. 装饰器的嵌套
装饰器可以嵌套使用,形成更复杂的功能。
# 装饰器的嵌套示例
import functools
def outer_decorator(func):
"""外部装饰器"""
@functools.wraps(func)
def outer_wrapper(*args, **kwargs):
"""外部包装函数"""
print("外部装饰器开始执行")
result = func(*args, **kwargs)
print("外部装饰器结束执行")
return result
return outer_wrapper
def inner_decorator(func):
"""内部装饰器"""
@functools.wraps(func)
def inner_wrapper(*args, **kwargs):
"""内部包装函数"""
print("内部装饰器开始执行")
result = func(*args, **kwargs)
print("内部装饰器结束执行")
return result
return inner_wrapper
# 使用嵌套装饰器
@outer_decorator
def decorated_function():
"""被装饰的函数"""
@inner_decorator
def inner_function():
"""内部函数"""
print("内部函数执行")
return "内部函数结果"
print("外部函数执行")
result = inner_function()
return f"外部函数结果: {result}"
# 调用装饰后的函数
result = decorated_function()
print(f"最终结果: {result}")
5. 装饰器与函数签名
装饰器需要正确处理函数的签名,以保持函数的元信息。
# 装饰器与函数签名示例
import functools
import inspect
def my_decorator(func):
"""不保留函数签名的装饰器"""
def wrapper(*args, **kwargs):
"""包装函数"""
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
def my_decorator_with_wraps(func):
"""保留函数签名的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@my_decorator
def func1(a, b, c=1):
"""测试函数1"""
return a + b + c
@my_decorator_with_wraps
def func2(a, b, c=1):
"""测试函数2"""
return a + b + c
# 查看函数签名
print(f"func1函数名: {func1.__name__}") # 输出:func1函数名: wrapper
print(f"func1文档字符串: {func1.__doc__}") # 输出:func1文档字符串: 包装函数
print(f"func1参数列表: {inspect.signature(func1)}") # 输出:func1参数列表: (*args, **kwargs)
print(f"\nfunc2函数名: {func2.__name__}") # 输出:func2函数名: func2
print(f"func2文档字符串: {func2.__doc__}") # 输出:func2文档字符串: 测试函数2
print(f"func2参数列表: {inspect.signature(func2)}") # 输出:func2参数列表: (a, b, c=1)
七、装饰器的实际应用场景
1. 日志记录
# 日志记录装饰器示例
import functools
import logging
import datetime
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def log_decorator(func):
"""记录函数调用的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
# 记录函数调用前的信息
logger.info(f"调用函数: {func.__name__}")
logger.info(f"参数: args={args}, kwargs={kwargs}")
# 调用原函数
start_time = datetime.datetime.now()
try:
result = func(*args, **kwargs)
# 记录函数调用成功的信息
logger.info(f"函数 {func.__name__} 调用成功")
logger.info(f"返回值: {result}")
return result
except Exception as e:
# 记录函数调用失败的信息
logger.error(f"函数 {func.__name__} 调用失败: {e}")
raise
finally:
# 记录函数调用结束的信息
end_time = datetime.datetime.now()
elapsed_time = end_time - start_time
logger.info(f"函数 {func.__name__} 执行时间: {elapsed_time.total_seconds():.4f}秒")
return wrapper
# 使用日志记录装饰器
@log_decorator
def divide(a, b):
"""计算两个数的商"""
return a / b
# 调用装饰后的函数
try:
result1 = divide(10, 2)
print(f"结果1: {result1}")
result2 = divide(10, 0) # 会抛出ZeroDivisionError异常
print(f"结果2: {result2}")
except Exception as e:
print(f"捕获到异常: {e}")
2. 性能分析
# 性能分析装饰器示例
import functools
import time
def performance_decorator(func):
"""计算函数执行时间的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"函数 {func.__name__} 执行时间: {elapsed_time:.4f}秒")
return result
return wrapper
# 使用性能分析装饰器
@performance_decorator
def slow_function(n):
"""模拟耗时操作"""
total = 0
for i in range(n):
total += i
return total
@performance_decorator
def fast_function(n):
"""使用更高效的方式计算"""
return n * (n - 1) // 2
# 调用装饰后的函数
result1 = slow_function(1000000)
print(f"结果1: {result1}")
result2 = fast_function(1000000)
print(f"结果2: {result2}")
3. 权限验证
# 权限验证装饰器示例
import functools
# 模拟用户信息
current_user = {"name": "张三", "role": "admin"}
def permission_required(role):
"""权限验证装饰器"""
def decorator(func):
"""装饰器函数"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
if current_user["role"] != role:
raise PermissionError(f"用户 {current_user['name']} 没有 {role} 权限")
print(f"用户 {current_user['name']} 有权限执行函数 {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
# 使用权限验证装饰器
@permission_required("admin")
def admin_function():
"""管理员功能"""
return "管理员功能执行成功"
@permission_required("user")
def user_function():
"""普通用户功能"""
return "普通用户功能执行成功"
# 调用装饰后的函数
try:
result1 = admin_function()
print(f"结果1: {result1}")
result2 = user_function()
print(f"结果2: {result2}")
except PermissionError as e:
print(f"权限错误: {e}")
# 切换用户
current_user = {"name": "李四", "role": "user"}
try:
result1 = admin_function()
print(f"结果1: {result1}")
except PermissionError as e:
print(f"权限错误: {e}")
try:
result2 = user_function()
print(f"结果2: {result2}")
except PermissionError as e:
print(f"权限错误: {e}")
4. 缓存
# 缓存装饰器示例
import functools
import time
def cache_decorator(func):
"""缓存函数返回结果的装饰器"""
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
# 生成缓存键
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
print(f"从缓存中获取 {func.__name__} 的结果")
return cache[key]
print(f"计算 {func.__name__} 的结果")
result = func(*args, **kwargs)
cache[key] = result
return result
# 添加清除缓存的方法
wrapper.clear_cache = lambda: cache.clear()
return wrapper
# 使用缓存装饰器
@cache_decorator
def expensive_function(n):
"""模拟昂贵的计算"""
print(f"执行昂贵的计算: n={n}")
time.sleep(1) # 模拟耗时操作
return n * 2
# 调用装饰后的函数
print("第一次调用:")
result1 = expensive_function(5)
print(f"结果1: {result1}")
print("\n第二次调用:")
result2 = expensive_function(5)
print(f"结果2: {result2}")
print("\n清除缓存:")
expensive_function.clear_cache()
print("\n第三次调用:")
result3 = expensive_function(5)
print(f"结果3: {result3}")
5. 重试机制
# 重试机制装饰器示例
import functools
import time
import random
def retry_decorator(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
"""重试装饰器"""
def decorator(func):
"""装饰器函数"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
attempts = 0
current_delay = delay
while attempts < max_attempts:
try:
print(f"尝试 {attempts + 1}/{max_attempts}")
return func(*args, **kwargs)
except exceptions as e:
attempts += 1
if attempts == max_attempts:
print(f"所有尝试都失败了,最终异常: {e}")
raise
print(f"尝试失败: {e},将在 {current_delay} 秒后重试")
time.sleep(current_delay)
current_delay *= backoff
return wrapper
return decorator
# 使用重试机制装饰器
@retry_decorator(max_attempts=5, delay=0.5, backoff=2, exceptions=(ValueError,))
def flaky_function():
"""模拟不稳定的函数"""
if random.random() < 0.7:
raise ValueError("函数执行失败")
return "函数执行成功"
# 调用装饰后的函数
try:
result = flaky_function()
print(f"最终结果: {result}")
except ValueError as e:
print(f"所有尝试都失败了: {e}")
八、装饰器的性能分析
1. 时间性能
装饰器会增加函数调用的开销,主要来自闭包的创建和调用。对于简单的装饰器,这种开销通常很小,可以忽略不计。但对于频繁调用的函数,装饰器的开销可能会累积。
# 装饰器的时间性能示例
import time
import functools
def simple_decorator(func):
"""简单装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def complex_decorator(func):
"""复杂装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 增加一些复杂的操作
for i in range(100):
pass
return func(*args, **kwargs)
return wrapper
# 测试函数
def test_function():
return 1
# 装饰函数
test_function1 = test_function
test_function2 = simple_decorator(test_function)
test_function3 = complex_decorator(test_function)
# 测试性能
n = 1000000
start_time = time.time()
for _ in range(n):
test_function1()
end_time = time.time()
print(f"原函数耗时: {end_time - start_time:.4f}秒")
start_time = time.time()
for _ in range(n):
test_function2()
end_time = time.time()
print(f"简单装饰器耗时: {end_time - start_time:.4f}秒")
start_time = time.time()
for _ in range(n):
test_function3()
end_time = time.time()
print(f"复杂装饰器耗时: {end_time - start_time:.4f}秒")
2. 内存性能
装饰器会创建额外的闭包,增加内存的使用。对于简单的装饰器,这种内存增加通常很小,但对于大量使用装饰器的程序,可能会有明显的内存影响。
# 装饰器的内存性能示例
import sys
import functools
def simple_decorator(func):
"""简单装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 测试函数
def test_function():
return 1
# 装饰函数
test_function1 = test_function
test_function2 = simple_decorator(test_function)
# 测试内存使用
print(f"原函数大小: {sys.getsizeof(test_function1)}字节")
print(f"装饰后函数大小: {sys.getsizeof(test_function2)}字节")
print(f"装饰器闭包大小: {sys.getsizeof(test_function2.__closure__)}字节")
九、装饰器的最佳实践
1. 保持装饰器简洁
装饰器应该保持简洁,只负责一个功能,避免实现复杂的逻辑。
# 推荐:简洁的装饰器
def log_decorator(func):
"""简单的日志记录装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
# 不推荐:复杂的装饰器
def complex_decorator(func):
"""复杂的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 实现多个功能
print(f"调用函数: {func.__name__}")
import time
start_time = time.time()
try:
result = func(*args, **kwargs)
print(f"函数执行成功")
return result
except Exception as e:
print(f"函数执行失败: {e}")
raise
finally:
end_time = time.time()
print(f"执行时间: {end_time - start_time:.4f}秒")
return wrapper
2. 使用functools.wraps
使用functools.wraps装饰器保留原函数的元信息,如函数名、文档字符串、参数列表等。
# 推荐:使用functools.wraps
import functools
def my_decorator(func):
"""装饰器"""
@functools.wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
"""包装函数"""
return func(*args, **kwargs)
return wrapper
3. 避免过度使用
装饰器虽然功能强大,但过度使用会使代码难以理解和调试。应该只在必要时使用装饰器。
4. 处理装饰器参数
装饰器参数应该有合理的默认值,并进行适当的验证。
# 推荐:合理处理装饰器参数
def retry_decorator(max_attempts=3, delay=1, exceptions=(Exception,)):
"""重试装饰器"""
# 验证参数
if max_attempts < 1:
raise ValueError("max_attempts必须大于0")
if delay < 0:
raise ValueError("delay必须大于等于0")
def decorator(func):
"""装饰器函数"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except exceptions as e:
attempts += 1
if attempts == max_attempts:
raise
time.sleep(delay)
return wrapper
return decorator
5. 考虑装饰器的可组合性
装饰器应该设计为可组合的,多个装饰器可以组合使用,形成更强大的功能。
# 推荐:可组合的装饰器
import functools
def log_decorator(func):
"""日志记录装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
def performance_decorator(func):
"""性能分析装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
import time
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"执行时间: {end_time - start_time:.4f}秒")
return result
return wrapper
# 组合使用装饰器
@log_decorator
@performance_decorator
def slow_function():
import time
time.sleep(1)
return "完成"
# 调用装饰后的函数
result = slow_function()
print(f"结果: {result}")
十、常见错误
1. 忘记调用装饰器工厂
当使用带参数的装饰器时,忘记调用装饰器工厂会导致错误。
# 错误:忘记调用装饰器工厂
def retry(max_attempts=3):
"""重试装饰器工厂"""
def decorator(func):
"""装饰器函数"""
def wrapper(*args, **kwargs):
"""包装函数"""
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise
return wrapper
return decorator
# 错误:忘记调用装饰器工厂
@retry # 应该是@retry()或@retry(5)
def flaky_function():
raise Exception("失败")
# 正确:调用装饰器工厂
@retry()
def flaky_function():
raise Exception("失败")
2. 装饰器参数错误
装饰器参数的类型或数量错误会导致错误。
# 错误:装饰器参数错误
@retry("3") # 应该是整数,不是字符串
def flaky_function():
raise Exception("失败")
@retry(max_attempts=3, delay=1, invalid_param=True) # 无效参数
def flaky_function():
raise Exception("失败")
3. 装饰器与继承
装饰器可能会影响类的继承,特别是当装饰器修改了方法的签名或行为时。
# 装饰器与继承示例
import functools
def my_decorator(func):
"""简单的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"装饰器执行")
return func(*args, **kwargs)
return wrapper
class Parent:
"""父类"""
@my_decorator
def method(self):
"""父类方法"""
return "父类方法"
class Child(Parent):
"""子类"""
def method(self):
"""子类方法"""
return "子类方法"
# 创建实例并调用方法
child = Child()
result = child.method()
print(f"结果: {result}") # 输出:子类方法(装饰器没有执行,因为子类重写了方法)
4. 装饰器的顺序错误
多个装饰器的顺序会影响函数的行为,顺序错误可能导致意外的结果。
# 装饰器的顺序错误示例
import functools
def decorator1(func):
"""装饰器1"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器1执行")
return func(*args, **kwargs)
return wrapper
def decorator2(func):
"""装饰器2"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器2执行")
return func(*args, **kwargs)
return wrapper
# 顺序1
@decorator1
@decorator2
def function1():
print("函数执行")
# 顺序2
@decorator2
@decorator1
def function2():
print("函数执行")
# 调用函数
print("顺序1:")
function1()
print("\n顺序2:")
function2()
十一、总结
装饰器是Python中一种强大而灵活的语法特性,它允许我们在不修改原有函数代码的情况下,为函数添加额外的功能。本文介绍了装饰器的以下内容:
1. 基本概念
- 装饰器是一个高阶函数,接受函数作为参数,并返回一个新函数
- 使用@符号语法,简化装饰器的使用
- 可以实现代码复用、解耦和简洁代码
2. 实现原理
- 函数对象:函数是一等公民,可以作为参数传递和返回
- 闭包:可以访问外部函数的变量
- 高阶函数:接受函数作为参数或返回函数
3. 基本用法
- 简单装饰器:不接受参数的装饰器
- 带参数的装饰器:接受参数的装饰器
- 多个装饰器:多个装饰器组合使用
- 装饰器链:装饰器的嵌套使用
4. 内置装饰器
- @staticmethod:静态方法
- @classmethod:类方法
- @property:属性访问器
- @functools.wraps:保留原函数的元信息
- @functools.lru_cache:缓存函数的返回结果
- @functools.singledispatch:单分派泛型函数
5. 高级用法
- 装饰器工厂:返回装饰器的函数
- 参数化装饰器:可以接受参数的装饰器
- 类装饰器:使用类实现装饰器
- 装饰器的嵌套:装饰器内部嵌套装饰器
- 装饰器与函数签名:保持函数的元信息
6. 实际应用场景
- 日志记录:记录函数的调用情况
- 性能分析:计算函数的执行时间
- 权限验证:检查用户是否有权限执行函数
- 缓存:缓存函数的返回结果
- 重试机制:在函数执行失败时自动重试
- 异常处理:捕获和处理函数执行过程中的异常
7. 最佳实践
- 保持装饰器简洁
- 使用functools.wraps
- 避免过度使用
- 处理装饰器参数
- 考虑装饰器的可组合性
8. 常见错误
- 忘记调用装饰器工厂
- 装饰器参数错误
- 装饰器与继承
- 装饰器的顺序错误
通过掌握装饰器的各种用法和最佳实践,可以编写出更简洁、更灵活、更易于维护的Python代码。装饰器是Python函数式编程的重要组成部分,也是Python语言的一大特色,值得深入学习和应用。
发布网站:荣殿教程(zhangrongdian.com)
作者:张荣殿