什么是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,输出会是wrapperWrapper 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开发者提升代码质量的重要一步。