详解 Python 装饰器

什么是装饰器?

装饰器(Decorator)是 Python 中一种强大的语法特性,它允许在不修改原函数代码的情况下,为函数或类添加额外的功能。装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。

装饰器的基本概念

1. 函数是一等公民

在 Python 中,函数是一等公民,这意味着:

  • 函数可以作为参数传递给其他函数
  • 函数可以作为其他函数的返回值
  • 函数可以赋值给变量

python

def greet(name):
    return f"Hello, {name}!"

# 函数赋值给变量
my_func = greet
print(my_func("Alice"))  # 输出: Hello, Alice!

# 函数作为参数
def call_twice(func, arg):
    return func(arg) + " " + func(arg)

print(call_twice(greet, "Bob"))  # 输出: Hello, Bob! Hello, Bob!

2. 闭包

闭包是理解装饰器的关键概念。闭包指的是内部函数可以访问外部函数作用域中的变量,即使外部函数已经执行完毕。

python

def outer_function(msg):
    def inner_function():
        print(msg)
    return inner_function

my_func = outer_function("Hello, Closure!")
my_func()  # 输出: Hello, Closure!

简单的装饰器实现

基础装饰器

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

def say_hello():
    print("Hello!")

# 使用装饰器
decorated_hello = my_decorator(say_hello)
decorated_hello()

输出:

text

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

使用 @ 语法糖

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()

处理带参数的函数

装饰带参数的函数

python

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function call")
        result = func(*args, **kwargs)
        print("After function call")
        return result
    return wrapper

@my_decorator
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Alice"))  # 输出: Hello, Alice!
print(greet("Bob", greeting="Hi"))  # 输出: Hi, Bob!

保留函数元信息

使用 functools.wraps 可以保留原函数的元信息:

python

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned")
        return result
    return wrapper

@my_decorator
def add(a, b):
    """Add two numbers"""
    return a + b

print(add.__name__)  # 输出: add
print(add.__doc__)   # 输出: Add two numbers

带参数的装饰器

有时候我们需要装饰器本身也能接受参数:

python

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(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")

输出:

text

Hello, Alice!
Hello, Alice!
Hello, Alice!

类装饰器

除了函数装饰器,还可以使用类来实现装饰器:

作为装饰器的类

python

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(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()  # 输出: Call 1 of say_hello \n Hello!
say_hello()  # 输出: Call 2 of say_hello \n Hello!

带参数的类装饰器

python

class DecoratorWithArgs:
    def __init__(self, prefix):
        self.prefix = prefix

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"{self.prefix}: Before function call")
            result = func(*args, **kwargs)
            print(f"{self.prefix}: After function call")
            return result
        return wrapper

@DecoratorWithArgs("DEBUG")
def multiply(a, b):
    return a * b

print(multiply(3, 4))

装饰器的实际应用场景

1. 日志记录

python

import time

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} executed in {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@log_execution_time
def slow_function():
    time.sleep(1)
    return "Done"

slow_function()

2. 权限验证

python

def require_login(func):
    @functools.wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated', False):
            raise PermissionError("User must be logged in")
        return func(user, *args, **kwargs)
    return wrapper

@require_login
def view_profile(user):
    return f"Viewing profile of {user['username']}"

user = {'username': 'alice', 'is_authenticated': True}
print(view_profile(user))

3. 缓存(记忆化)

python

def cache(func):
    cached_results = {}
    
    @functools.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))

4. 重试机制

python

import random

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        raise e
                    print(f"Attempt {attempts} failed: {e}. Retrying in {delay} seconds...")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=1)
def unreliable_function():
    if random.random() < 0.7:
        raise Exception("Something went wrong!")
    return "Success!"

print(unreliable_function())

多个装饰器的执行顺序

当使用多个装饰器时,它们按照从下往上的顺序执行:

python

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 say_hello():
    print("Hello!")

say_hello()

输出:

text

Decorator 1 - before
Decorator 2 - before
Hello!
Decorator 2 - after
Decorator 1 - after

内置装饰器

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

@staticmethod 和 @classmethod

python

class MyClass:
    class_attribute = "class value"
    
    def __init__(self, value):
        self.instance_attribute = value
    
    @staticmethod
    def static_method():
        return "This is a static method"
    
    @classmethod
    def class_method(cls):
        return f"This is a class method. Class attribute: {cls.class_attribute}"
    
    def instance_method(self):
        return f"This is an instance method. Instance attribute: {self.instance_attribute}"

print(MyClass.static_method())
print(MyClass.class_method())
obj = MyClass("instance value")
print(obj.instance_method())

@property

python

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 cannot be negative")
        self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2
    
    @property
    def diameter(self):
        return 2 * self._radius

circle = Circle(5)
print(f"Radius: {circle.radius}")
print(f"Area: {circle.area}")
circle.radius = 10
print(f"New area: {circle.area}")

装饰器的最佳实践

  1. 总是使用 functools.wraps:保留原函数的元信息
  2. 保持装饰器简单:每个装饰器应该只负责一个明确的功能
  3. 考虑可测试性:确保装饰后的函数仍然易于测试
  4. 文档化装饰器:说明装饰器的用途和效果
  5. 注意执行顺序:多个装饰器的执行顺序很重要

总结

Python 装饰器是一种强大的元编程工具,它允许我们以声明性的方式修改或增强函数和类的行为。通过理解闭包、高阶函数和装饰器语法,你可以创建出简洁、可重用的代码。装饰器在日志记录、权限验证、缓存、性能监控等场景中有着广泛的应用,是每个 Python 开发者都应该掌握的重要概念。

装饰器的学习曲线可能有些陡峭,但一旦掌握,它们将成为你工具箱中不可或缺的强大工具,帮助你编写更加模块化、可维护的 Python 代码。