什么是装饰器?
装饰器(Decorator)是Python中一种强大的语法特性,它允许在不修改原有函数代码的情况下,动态地改变函数的行为。装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。这种设计模式遵循了开闭原则(对扩展开放,对修改关闭),是实现横切关注点(如日志记录、性能监控、权限验证等)的理想工具。
想象一下,你有一台咖啡机(原始函数),现在想给它添加一个自动关机功能。装饰器就像一个智能插头,你只需把咖啡机插进这个插头,就能获得新功能,而无需拆解咖啡机本身。
装饰器的核心原理
要理解装饰器,需要掌握两个Python核心概念:
- 函数是一等公民:函数可以作为参数传递,也可以作为返回值
- 闭包:内部函数可以访问外部函数的变量
# 基本装饰器示例
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 函数执行前
# Hello!
# 函数执行后
带参数的函数装饰
当被装饰的函数需要参数时,装饰器内部的包装函数需要使用*args和**kwargs来接收参数:
def parameter_decorator(func):
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}, 参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"函数返回: {result}")
return result
return wrapper
@parameter_decorator
def add(a, b):
return a + b
print(add(3, 5))
# 输出:
# 调用函数: add, 参数: (3, 5), {}
# 函数返回: 8
# 8
类装饰器
除了函数装饰器,Python还支持类装饰器。类装饰器需要实现call方法,使其实例可以像函数一样被调用:
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 被调用了 1 次
# Hello!
# 函数 say_hello 被调用了 2 次
# Hello!
带参数的装饰器
有时我们需要给装饰器本身传递参数,这需要创建一个三层嵌套函数:
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}!")
greet("Alice")
# 输出:
# Hello Alice!
# Hello Alice!
# Hello Alice!
装饰器的常见应用场景
- 日志记录
def log_execution(func):
import logging
logging.basicConfig(level=logging.INFO)
def wrapper(*args, **kwargs):
logging.info(f"执行函数 {func.__name__},参数: {args}, {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
@log_execution
def divide(a, b):
return a / b
divide(10, 2)
- 性能计时
import time
def timer(func):
import functools
@functools.wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 执行耗时: {end - start:.4f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function()
- 权限验证
def requires_admin(func):
def wrapper(user, *args, **kwargs):
if user.get("role") != "admin":
raise PermissionError("需要管理员权限")
return func(user, *args, **kwargs)
return wrapper
@requires_admin
def delete_user(user, target_user):
print(f"用户 {user['name']} 删除了 {target_user}")
admin_user = {"name": "Alice", "role": "admin"}
regular_user = {"name": "Bob", "role": "user"}
delete_user(admin_user, "Charlie") # 成功
# delete_user(regular_user, "Charlie") # 抛出PermissionError
内置装饰器
Python提供了一些内置装饰器:
@property: 将方法转换为属性@classmethod: 类方法@staticmethod: 静态方法@functools.wraps: 保留被装饰函数的元信息
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("半径必须为正数")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 5
c.radius = 10
print(c.area) # 314.159
装饰器的高级技巧
- 多个装饰器堆叠使用
def make_bold(func):
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def make_italic(func):
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@make_bold
@make_italic
def greet():
return "Hello!"
print(greet()) # <b><i>Hello!</i></b>
- 使用类作为装饰器
class Memoize:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args in self.cache:
return self.cache[args]
result = self.func(*args)
self.cache[args] = result
return result
@Memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 55
装饰器的最佳实践
- 使用functools.wraps
from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@debug
def add(a, b):
"""计算两个数的和"""
return a + b
print(add.__name__) # 输出"add"而不是"wrapper"
print(add.__doc__) # 输出"计算两个数的和"
保持装饰器简单 每个装饰器应该只做一件事,遵循单一职责原则。
考虑装饰器的执行顺序 装饰器从下往上执行(靠近函数的先执行),从上往下包装(远离函数的最后包装)。
总结
装饰器是Python中非常强大的工具,它能:
- 增强函数功能而不修改原代码
- 保持代码的DRY(Don’t Repeat Yourself)原则
- 提高代码的可读性和可维护性
掌握装饰器需要理解函数作为一等公民、闭包等概念。从简单的函数装饰器开始,逐步学习带参数的装饰器、类装饰器,最后探索装饰器的高级应用。记住,装饰器应该用于增强函数,而不是改变函数的核心逻辑。
通过合理使用装饰器,你可以创建出更加模块化、可重用的代码,大大提高开发效率。
