引言
Python装饰器(Decorator)是Python语言中一个强大而优雅的特性,它允许我们在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数并返回一个新的函数。这种设计模式体现了软件工程中的”开闭原则”——对扩展开放,对修改关闭。
装饰器的语法使用@符号,这使得代码更加简洁和易读。通过装饰器,我们可以轻松实现日志记录、性能测试、权限验证、缓存等通用功能,从而避免代码重复,提高代码的可维护性。
装饰器的基本概念
什么是装饰器
装饰器是一个可调用的对象(如函数),它接收一个函数作为输入,并返回一个新的函数。这个新函数通常会在原始函数的前后添加一些额外的逻辑。
最简单的装饰器示例
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用装饰后的函数
say_hello()
输出结果:
函数执行前
Hello!
函数执行后
装饰器的工作原理
当我们使用@my_decorator语法时,Python实际上执行了以下操作:
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
带参数的函数装饰器
处理带参数的函数
当被装饰的函数带有参数时,我们需要在包装函数中也接受相应的参数:
def greet_decorator(func):
def wrapper(*args, **kwargs):
print("准备问候...")
result = func(*args, **kwargs)
print("问候完成!")
return result
return wrapper
@greet_decorator
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice")
greet("Bob", "Hi")
输出:
准备问候...
Hello, Alice!
问候完成!
准备问候...
Hi, Bob!
问候完成!
带参数的装饰器
有时我们需要为装饰器本身传递参数,这需要创建一个返回装饰器的函数:
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello, {name}!")
greet("World")
输出:
Hello, World!
Hello, World!
Hello, World!
装饰器的实际应用场景
1. 日志记录
import logging
from functools import wraps
def log_execution(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(func.__name__)
logger.info(f"开始执行函数: {func.__name__}")
try:
result = func(*args, **kwargs)
logger.info(f"函数执行成功: {func.__name__}")
return result
except Exception as e:
logger.error(f"函数执行失败: {func.__name__}, 错误: {e}")
raise
return wrapper
@log_execution
def divide(a, b):
return a / b
# 测试
print(divide(10, 2))
# print(divide(10, 0)) # 这会触发错误日志
2. 性能测试
import time
from functools import wraps
def timing(func):
@wraps(func)
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
@timing
def slow_function():
time.sleep(1)
return "完成"
slow_function()
3. 权限验证
from functools import wraps
def require_admin(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") != "admin":
raise PermissionError("需要管理员权限")
return func(user, *args, **kwargs)
return wrapper
@require_admin
def delete_user(user, target_user):
print(f"用户 {user['name']} 删除了 {target_user}")
# 测试
admin = {"name": "Alice", "role": "admin"}
user = {"name": "Bob", "role": "user"}
delete_user(admin, "Charlie") # 成功
# delete_user(user, "Charlie") # 抛出PermissionError
4. 缓存/记忆化
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 快速计算,因为使用了缓存
类装饰器
基本类装饰器
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"函数 {self.func.__name__} 被调用了 {self.num_calls} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
say_hello()
输出:
函数 say_hello 被调用了 1 次
Hello!
函数 say_hello 被调用了 2 次
Hello!
函数 say_hello 被调用了 3 次
Hello!
带参数的类装饰器
class Retry:
def __init__(self, max_retries=3):
self.max_retries = max_retries
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(self.max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == self.max_retries - 1:
raise
print(f"尝试 {attempt + 1} 失败,重试...")
return None
return wrapper
@Retry(max_retries=3)
def unstable_function():
import random
if random.random() < 0.7:
raise ValueError("随机失败")
return "成功"
# 多次运行可能得到不同结果
print(unstable_function())
内置装饰器
@property
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
@property
def perimeter(self):
return 2 * 3.14159 * self.radius
circle = Circle(5)
print(f"半径: {circle.radius}")
print(f"面积: {circle.area}")
print(f"周长: {circle.perimeter}")
# 可以像访问属性一样使用,不需要括号
@classmethod 和 @staticmethod
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
from datetime import datetime
current_year = datetime.now().year
age = current_year - birth_year
return cls(name, age)
@staticmethod
def is_adult(age):
return age >= 18
# 使用类方法
person1 = Person("Alice", 25)
person2 = Person.from_birth_year("Bob", 2000)
print(person1.name, person1.age)
print(person2.name, person2.age)
# 使用静态方法
print(Person.is_adult(20)) # True
print(Person.is_adult(15)) # False
高级装饰器技术
装饰器堆叠(多个装饰器一起使用)
def bold(func):
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def italic(func):
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@bold
@italic
def hello():
return "Hello World"
print(hello()) # 输出: <b><i>Hello World</i></b>
使用functools.wraps
from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__} 参数: {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
@debug
def add(a, b):
"""计算两个数的和"""
return a + b
print(add.__name__) # 输出: add (如果没有@wraps会输出wrapper)
print(add.__doc__) # 输出: 计算两个数的和
装饰器类参数
class ValidateInput:
def __init__(self, min_val=None, max_val=None):
self.min_val = min_val
self.max_val = max_val
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
for arg in args:
if self.min_val is not None and arg < self.min_val:
raise ValueError(f"参数 {arg} 小于最小值 {self.min_val}")
if self.max_val is not None and arg > self.max_val:
raise ValueError(f"参数 {arg} 大于最大值 {self.max_val}")
return func(*args, **kwargs)
return wrapper
@ValidateInput(min_val=0, max_val=100)
def set_percentage(value):
print(f"设置百分比为: {value}%")
set_percentage(50) # 正常
# set_percentage(150) # 抛出ValueError
装饰器的最佳实践
1. 保持装饰器的单一职责
# 不好的做法:一个装饰器做太多事情
def bad_decorator(func):
def wrapper(*args, **kwargs):
# 日志
print("日志...")
# 验证
if not check_permission():
raise PermissionError()
# 计时
start = time.time()
result = func(*args, **kwargs)
print(f"耗时: {time.time() - start}")
return result
return wrapper
# 好的做法:拆分成多个装饰器
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"耗时: {time.time() - start}")
return result
return wrapper
2. 文档和类型提示
from typing import Callable, Any
from functools import wraps
def validate_type(*expected_types: type) -> Callable:
"""
验证函数参数类型的装饰器
Args:
*expected_types: 期望的参数类型
Returns:
Callable: 装饰后的函数
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if len(args) != len(expected_types):
raise ValueError("参数数量不匹配")
for arg, expected in zip(args, expected_types):
if not isinstance(arg, expected):
raise TypeError(f"期望 {expected}, 得到 {type(arg)}")
return func(*args, **kwargs)
return wrapper
return decorator
@validate_type(int, int)
def multiply(a: int, b: int) -> int:
"""两个整数相乘"""
return a * b
print(multiply(3, 4)) # 12
# multiply("3", 4) # TypeError
3. 装饰器的性能考虑
import time
from functools import wraps
# 高性能缓存装饰器(使用LRU缓存)
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_calculation(n):
"""模拟耗时计算"""
time.sleep(0.1)
return n * n
# 第一次调用会执行计算
print(expensive_calculation(5))
# 第二次调用直接返回缓存结果
print(expensive_calculation(5))
# 查看缓存信息
print(expensive_calculation.cache_info())
装饰器的调试技巧
1. 使用name和doc属性
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""这是一个示例函数"""
pass
print(example.__name__) # example
print(example.__doc__) # 这是一个示例函数
2. 装饰器调试示例
def verbose_decorator(func):
"""详细的调试装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
print(f"\n=== 调试信息 ===")
print(f"函数名: {func.__name__}")
print(f"位置参数: {args}")
print(f"关键字参数: {kwargs}")
print(f"函数文档: {func.__doc__}")
result = func(*args, **kwargs)
print(f"返回值: {result}")
print(f"=== 调试结束 ===\n")
return result
return wrapper
@verbose_decorator
def complex_calculation(x, y, operation="add"):
"""执行复杂的数学运算"""
if operation == "add":
return x + y
elif operation == "multiply":
return x * y
complex_calculation(5, 3, operation="multiply")
实际项目中的装饰器应用
Web框架中的路由装饰器
class Router:
def __init__(self):
self.routes = {}
def route(self, path):
def decorator(func):
self.routes[path] = func
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
def handle_request(self, path):
if path in self.routes:
return self.routes[path]()
else:
return "404 Not Found"
router = Router()
@router.route("/")
def home():
return "Welcome to Home Page"
@router.route("/about")
def about():
return "About Us"
# 模拟请求
print(router.handle_request("/")) # Welcome to Home Page
print(router.handle_request("/about")) # About Us
print(router.handle_request("/contact")) # 404 Not Found
数据库连接管理
from functools import wraps
import sqlite3
def with_db_connection(db_path):
"""数据库连接装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
conn = sqlite3.connect(db_path)
try:
result = func(conn, *args, **kwargs)
conn.commit()
return result
finally:
conn.close()
return wrapper
return decorator
@with_db_connection("example.db")
def create_table(conn):
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
)
""")
print("表创建成功")
@with_db_connection("example.db")
def insert_user(conn, name, email):
cursor = conn.cursor()
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", (name, email))
print(f"用户 {name} 插入成功")
# 使用
create_table()
insert_user("Alice", "alice@example.com")
总结
装饰器是Python中一个非常强大和优雅的特性,它允许我们以声明式的方式为函数添加功能,而无需修改函数的内部实现。通过本文的详细讲解和丰富的示例,我们学习了:
- 基础概念:装饰器的本质是高阶函数,使用
@语法糖简化使用 - 基本用法:如何创建简单的装饰器,处理带参数的函数
- 高级特性:带参数的装饰器、类装饰器、内置装饰器
- 实际应用:日志记录、性能测试、权限验证、缓存等场景
- 最佳实践:保持单一职责、使用functools.wraps、添加文档和类型提示
- 高级技巧:装饰器堆叠、调试技巧、项目中的实际应用
装饰器的核心优势在于:
- 代码复用:避免重复代码
- 关注点分离:将核心逻辑与横切关注点(如日志、验证)分离
- 可读性:使用
@语法使代码意图更清晰 - 灵活性:可以轻松组合和重用
掌握装饰器将大大提升你的Python编程能力,使代码更加优雅、可维护和可扩展。在实际项目中,合理使用装饰器可以显著提高开发效率和代码质量。
