Python 装饰器(Decorators)是 Python 语言中一个强大且优雅的特性,它允许我们在不修改原有函数代码的情况下,动态地改变函数的行为。装饰器广泛应用于日志记录、权限验证、性能监控、缓存等场景。本文将从基础概念入手,逐步深入到高级应用,帮助你全面掌握 Python 装饰器的使用。
什么是装饰器?
装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常会在调用原始函数前后执行一些额外的逻辑。装饰器的核心思想是“包装”——将原函数包裹在另一个函数中,从而在不改变原函数代码的前提下扩展其功能。
装饰器的语法
在 Python 中,装饰器使用 @ 符号来应用。例如:
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
输出:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
这里,@my_decorator 就是装饰器的语法糖,它等价于 say_hello = my_decorator(say_hello)。
装饰器的工作原理
要理解装饰器,首先需要理解 Python 中的函数是一等公民(first-class objects)。这意味着函数可以作为参数传递给其他函数,也可以作为返回值从函数中返回。
高阶函数
高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。装饰器就是一个典型的高阶函数。
def greet(name):
return f"Hello, {name}!"
def loud_greet(func):
def wrapper(name):
return func(name).upper() + "!!!"
return wrapper
greet = loud_greet(greet)
print(greet("Alice")) # 输出: HELLO, ALICE!!!!
闭包
装饰器通常利用闭包(closure)来保存状态。闭包是指一个函数引用了它外部作用域的变量,即使外部函数已经执行完毕。
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c = counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
带参数的装饰器
有时候,我们需要装饰器本身也能接受参数。这可以通过在装饰器外面再包装一层函数来实现。
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("Bob")
输出:
Hello, Bob!
Hello, Bob!
Hello, Bob!
类装饰器
除了函数装饰器,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"Call {self.num_calls} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
输出:
Call 1 of say_hello
Hello!
Call 2 of say_hello
Hello!
装饰器的常见应用场景
1. 日志记录
装饰器可以方便地记录函数的调用信息。
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_function_call
def add(a, b):
return a + b
add(3, 5)
输出:
Calling add with args=(3, 5), kwargs={}
add returned 8
2. 权限验证
在 Web 开发中,装饰器常用于检查用户权限。
def requires_admin(func):
def wrapper(user, *args, **kwargs):
if user.get("role") != "admin":
raise PermissionError("Admin access required")
return func(user, *args, **kwargs)
return wrapper
@requires_admin
def delete_user(user, username):
print(f"User {username} deleted by {user['name']}")
admin_user = {"name": "Alice", "role": "admin"}
regular_user = {"name": "Bob", "role": "user"}
delete_user(admin_user, "charlie") # Works
delete_user(regular_user, "dave") # Raises PermissionError
3. 性能监控
装饰器可以用来测量函数的执行时间。
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function()
输出:
slow_function took 1.0012 seconds
4. 缓存
装饰器可以用于缓存函数的结果,避免重复计算。
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(30)) # 计算速度快
装饰器的高级技巧
1. 使用 functools.wraps
当使用装饰器时,原函数的元信息(如函数名、文档字符串)会被替换为包装函数的信息。使用 functools.wraps 可以保留原函数的元信息。
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Example function"""
pass
print(example.__name__) # 输出: example
print(example.__doc__) # 输出: Example function
2. 多个装饰器的组合
多个装饰器可以堆叠使用,它们的执行顺序是从下往上(靠近函数的装饰器先执行)。
def decorator1(func):
def wrapper():
print("Decorator 1 before")
func()
print("Decorator 1 after")
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2 before")
func()
print("Decorator 2 after")
return wrapper
@decorator1
@decorator2
def my_function():
print("Function executed")
my_function()
输出:
Decorator 1 before
Decorator 2 before
Function executed
Decorator 2 after
Decorator 1 after
3. 类方法装饰器
装饰器也可以用于类的方法,但需要注意 self 参数。
def method_decorator(func):
def wrapper(self, *args, **kwargs):
print(f"Calling {func.__name__} on {self}")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
@method_decorator
def my_method(self, x):
return x * 2
obj = MyClass()
print(obj.my_method(5)) # 输出: 10
4. 装饰器带参数和实例
有时候,我们需要装饰器既能接受参数,又能作用于类实例。
class DecoratorWithArgs:
def __init__(self, arg):
self.arg = arg
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f"Decorator arg: {self.arg}")
return func(*args, **kwargs)
return wrapper
@DecoratorWithArgs("test")
def my_func():
print("Inside my_func")
my_func()
输出:
Decorator arg: test
Inside my_func
装饰器的注意事项
1. 调试困难
装饰器会改变函数的调用栈,使得调试变得困难。使用 functools.wraps 可以部分缓解这个问题。
2. 性能开销
装饰器会增加函数调用的开销,特别是在多层装饰器的情况下。在性能敏感的场景下需要谨慎使用。
3. 保持接口一致
确保装饰器返回的函数与原函数具有相同的签名,或者使用 *args 和 **kwargs 来保持通用性。
实际案例:构建一个完整的缓存装饰器
让我们构建一个功能完整的缓存装饰器,支持过期时间、最大缓存大小等参数。
import time
from collections import OrderedDict
from functools import wraps
def cache_with_expiry(expiry_seconds=60, max_size=100):
"""
带过期时间和大小限制的缓存装饰器
"""
def decorator(func):
# 使用 OrderedDict 来维护缓存顺序
cache = OrderedDict()
@wraps(func)
def wrapper(*args, **kwargs):
# 创建缓存键(简化版本,实际应用中需要处理更复杂的情况)
key = str(args) + str(kwargs)
# 检查缓存是否存在且未过期
if key in cache:
result, timestamp = cache[key]
if time.time() - timestamp < expiry_seconds:
# 移动到末尾,表示最近使用
cache.move_to_end(key)
return result
else:
# 过期,删除
del cache[key]
# 调用原函数
result = func(*args, **kwargs)
# 检查缓存大小
if len(cache) >= max_size:
# 删除最旧的缓存项
cache.popitem(last=False)
# 添加到缓存
cache[key] = (result, time.time())
return result
# 添加缓存清除方法
def clear_cache():
cache.clear()
wrapper.clear_cache = clear_cache
return wrapper
return decorator
# 使用示例
@cache_with_expiry(expiry_seconds=10, max_size=3)
def expensive_operation(x, y):
print(f"Computing {x} + {y}")
time.sleep(0.1) # 模拟耗时操作
return x + y
# 第一次调用,会执行计算
print(expensive_operation(1, 2)) # Computing 1 + 2 \n 3
# 第二次调用相同参数,会使用缓存
print(expensive_operation(1, 2)) # 3 (无打印)
# 不同参数,会执行计算
print(expensive_operation(2, 3)) # Computing 2 + 3 \n 5
# 清除缓存
expensive_operation.clear_cache()
print(expensive_operation(1, 2)) # Computing 1 + 2 \n 3
总结
Python 装饰器是一个强大而灵活的工具,它允许我们以声明式的方式为函数添加额外功能。通过本文的介绍,你应该已经掌握了:
- 基础概念:装饰器的定义、语法和工作原理
- 进阶用法:带参数的装饰器、类装饰器、装饰器组合
- 实际应用:日志记录、权限验证、性能监控、缓存等场景
- 高级技巧:使用
functools.wraps、处理类方法、构建复杂装饰器 - 注意事项:调试、性能、接口一致性等问题
装饰器是 Pythonic 编程风格的体现,合理使用装饰器可以让你的代码更加简洁、可读和可维护。记住,装饰器的核心思想是“包装”和“扩展”,而不是“修改”。当你发现自己在多个函数中重复相同的代码模式时,考虑使用装饰器来抽象这些模式。
最后,建议在实际项目中多练习使用装饰器,从简单的日志记录开始,逐步尝试更复杂的场景。随着经验的积累,你会发现装饰器是 Python 编程中不可或缺的利器。
