什么是装饰器?

装饰器(Decorator)是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.

装饰器的类型

1. 函数装饰器

这是最常见的装饰器类型,用于修改或增强函数的行为。

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time:.4f} seconds to run.")
        return result
    return wrapper

@timing_decorator
def slow_function():
    time.sleep(2)
    print("Function completed.")

slow_function()

2. 类装饰器

类装饰器使用类来实现,通常用于需要维护状态的场景。

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!

带参数的装饰器

有时我们需要创建可以接受参数的装饰器,这需要额外一层嵌套函数。

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!

装饰器的实用场景

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)

2. 权限验证

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": "John", "role": "admin"}
regular_user = {"name": "Jane", "role": "user"}

delete_user(admin_user, "bad_actor")  # Works
# delete_user(regular_user, "bad_actor")  # Raises PermissionError

3. 缓存/记忆化

def memoize(func):
    cache = {}
    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))  # Much faster than without memoization

内置装饰器

Python提供了一些内置装饰器:

1. @property

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("Radius must be positive")
        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

2. @classmethod 和 @staticmethod

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @classmethod
    def from_fahrenheit(cls, fahrenheit):
        return cls((fahrenheit - 32) * 5/9)

    @staticmethod
    def is_freezing(celsius):
        return celsius <= 0

t1 = Temperature(25)
t2 = Temperature.from_fahrenheit(77)
print(t2.celsius)  # 25.0
print(Temperature.is_freezing(5))  # False

高级装饰器技巧

1. 装饰器堆叠

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>

2. 保留元数据

使用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

3. 类方法装饰器

class MyClass:
    def __init__(self):
        self._value = 0

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value

    def __str__(self):
        return f"MyClass(value={self._value})"

# 使用装饰器增强类方法
def validate_positive(func):
    @wraps(func)
    def wrapper(self, value):
        if value <= 0:
            raise ValueError("Value must be positive")
        return func(self, value)
    return wrapper

# 动态添加装饰器到类方法
MyClass.value = property(MyClass.value.fget, validate_positive(MyClass.value.fset))

装饰器的性能考虑

虽然装饰器非常强大,但它们也会带来一些性能开销:

import time
from functools import wraps

def heavy_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 模拟一些开销
        time.sleep(0.001)
        return func(*args, **kwargs)
    return wrapper

@heavy_decorator
def fast_function():
    return "Fast"

def normal_function():
    return "Fast"

# 性能比较
start = time.time()
for _ in range(1000):
    fast_function()
decorator_time = time.time() - start

start = time.time()
for _ in range(1000):
    normal_function()
normal_time = time.time() - start

print(f"With decorator: {decorator_time:.4f}s")
print(f"Without decorator: {normal_time:.4f}s")

最佳实践

  1. 保持装饰器简单:每个装饰器应该只做一件事
  2. 使用functools.wraps:保留原始函数的元数据
  3. 文档化装饰器:清楚地说明装饰器的行为
  4. 考虑可读性:复杂的装饰器可能降低代码可读性
  5. 测试装饰器:确保装饰器在各种情况下都能正常工作

结论

装饰器是Python中一个非常强大的特性,它们提供了一种优雅的方式来扩展和修改函数和方法的行为。通过理解装饰器的工作原理和各种使用场景,你可以编写出更加模块化、可重用和易于维护的代码。记住,装饰器应该被用来增强代码的功能,而不是使其复杂化。当使用得当时,装饰器可以极大地提高代码的质量和可读性。