什么是Python装饰器?
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作为参数,并返回一个新的函数wrapper。当say_hello()被调用时,实际上调用的是wrapper函数,它在调用say_hello前后添加了额外的打印语句。
带参数的函数装饰
如果被装饰的函数带有参数,我们需要在包装函数中处理这些参数:
def greet_decorator(func):
def wrapper(*args, **kwargs):
print("Greeting...")
result = func(*args, **kwargs)
print("Greeting completed!")
return result
return wrapper
@greet_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
输出:
Greeting...
Hello, Alice!
Greeting completed!
这里使用了*args和**kwargs来接收任意数量的位置参数和关键字参数,使得装饰器可以适用于任何函数签名。
类装饰器
除了函数装饰器,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_execution(func):
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}, result: {result}")
return result
return wrapper
@log_execution
def add(a, b):
return a + b
add(3, 5)
输出:
Executing add with args: (3, 5), kwargs: {}
Finished add, result: 8
2. 性能测试
使用装饰器可以轻松测量函数执行时间:
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
3. 权限验证
在Web开发中,装饰器常用于检查用户权限:
def requires_admin(func):
def wrapper(user, *args, **kwargs):
if user.is_admin:
return func(user, *args, **kwargs)
else:
raise PermissionError("Admin access required")
return wrapper
class User:
def __init__(self, name, is_admin):
self.name = name
self.is_admin = is_admin
@requires_admin
def delete_user(admin_user, user_to_delete):
print(f"{admin_user.name} deleted {user_to_delete.name}")
admin = User("Alice", True)
regular_user = User("Bob", False)
other_user = User("Charlie", True)
delete_user(admin, other_user) # Works
try:
delete_user(regular_user, other_user) # Fails
except PermissionError as e:
print(e)
输出:
Alice deleted Charlie
Admin access required
带参数的装饰器
有时候我们需要让装饰器本身接受参数,这需要多一层包装:
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("World")
输出:
Hello, World!
Hello, World!
Hello, World!
这里repeat是一个装饰器工厂函数,它接受参数并返回实际的装饰器。
保留原函数元数据
使用装饰器时,原函数的元数据(如函数名、文档字符串等)会被包装函数覆盖。为了解决这个问题,Python提供了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
如果不使用@wraps,输出会是wrapper和Wrapper function。
类方法装饰器
装饰器也可以应用于类方法:
def method_decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
print(f"Calling method {func.__name__} on {self}")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
def __init__(self, value):
self.value = value
@method_decorator
def get_value(self):
return self.value
obj = MyClass(42)
print(obj.get_value())
输出:
Calling method get_value on <__main__.MyClass object at 0x...>
42
静态方法和类方法装饰器
装饰器同样适用于静态方法和类方法:
class MyClass:
@staticmethod
@method_decorator
def static_method():
print("Static method called")
@classmethod
@method_decorator
def class_method(cls):
print(f"Class method called on {cls}")
MyClass.static_method()
MyClass.class_method()
装饰器的顺序
当多个装饰器堆叠时,它们的应用顺序是从下到上(从最接近函数的开始):
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Decorator 1 - Before")
result = func(*args, **kwargs)
print("Decorator 1 - After")
return result
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Decorator 2 - Before")
result = func(*args, **kwargs)
print("Decorator 2 - After")
return result
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
实际案例:缓存装饰器
下面是一个实用的缓存装饰器实现,用于存储函数结果以避免重复计算:
from functools import wraps
def cache(func):
cached_results = {}
@wraps(func)
def wrapper(*args):
if args in cached_results:
print(f"Returning cached result for {args}")
return cached_results[args]
result = func(*args)
cached_results[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 计算并缓存
print(fibonacci(10)) # 直接从缓存读取
这个缓存装饰器显著提高了递归函数的性能,避免了重复计算。
装饰器与设计模式
装饰器模式是面向对象设计中的经典模式,Python的装饰器语法使其实现变得非常简洁。这种模式允许在不改变对象接口的情况下,动态地添加功能。装饰器在Python标准库中广泛应用,如@property、@staticmethod、@classmethod等。
总结
Python装饰器是一个强大而灵活的工具,它可以帮助我们:
- 保持代码的简洁性
- 实现关注点分离
- 提高代码的复用性
- 动态地扩展函数功能
通过合理使用装饰器,我们可以编写出更加模块化、可维护的代码。从简单的日志记录到复杂的权限控制,装饰器都能提供优雅的解决方案。掌握装饰器的使用是每个Python开发者提升代码质量的重要一步。
