Python装饰器:从基础到高级的全面指南

  1. 引言

Python装饰器是一种强大而灵活的编程工具,它允许我们以一种优雅的方式修改或增强函数、方法或类的行为,而无需直接修改它们的源代码。装饰器在Python中被广泛应用,从简单的日志记录到复杂的缓存机制,再到Web框架中的路由定义,无处不在。

本文将深入探讨Python装饰器的工作原理、常见用法、高级技巧以及实际应用场景。我们将通过大量的代码示例和详细解释,帮助您全面理解装饰器的概念和应用。

2.装饰器基础

2.1 什么是装饰器?

装饰器本质上是一个可调用对象(通常是一个函数),它接受另一个函数作为参数,并返回一个新的函数。这个新函数通常会在执行原始函数前后添加一些额外的功能。

让我们从一个简单的例子开始:

def simple_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  # 返回包装函数

@simple_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. simple_decorator 是一个装饰器函数。它接受一个函数 func 作为参数。
  2. 在装饰器内部,我们定义了一个新的函数 wrapper。这个函数会在调用原始函数前后执行一些额外的代码。
  3. wrapper 函数内部调用了原始的 func 函数。
  4. 装饰器返回 wrapper 函数,而不是原始的 func 函数。
  5. @simple_decorator 语法糖等同于 say_hello = simple_decorator(say_hello)。这意味着 say_hello 函数被 simple_decorator 装饰后,实际上变成了 wrapper 函数。
  6. 当我们调用 say_hello() 时,实际上是在调用 wrapper 函数。

这个例子展示了装饰器的基本工作原理:它可以在不修改原始函数代码的情况下,为函数添加新的功能。

2.2 装饰器的工作原理

为了更好地理解装饰器的工作原理,让我们来看看不使用 @ 语法时如何应用装饰器:

def uppercase_decorator(func):
    def wrapper():
        result = func()  # 调用原始函数
        return result.upper()  # 将结果转换为大写
    return wrapper  # 返回新的函数

def greet():
    return "hello, world!"

# 手动应用装饰器
decorated_greet = uppercase_decorator(greet)

# 调用装饰后的函数
print(decorated_greet())  # 输出: HELLO, WORLD!

理解这段代码:

  1. uppercase_decorator 是一个装饰器函数,它接受一个函数 func 作为参数。
  2. 在装饰器内部,我们定义了 wrapper 函数。这个函数调用原始的 func 函数,然后将结果转换为大写。
  3. 装饰器返回 wrapper 函数。
  4. 我们手动将 greet 函数传递给 uppercase_decorator,得到一个新的函数 decorated_greet
  5. 当我们调用 decorated_greet() 时,实际上是在调用 wrapper 函数,它会执行原始的 greet 函数,然后将结果转换为大写。

这个例子展示了装饰器的核心原理:它接受一个函数,返回一个新的函数,这个新函数通常会在调用原始函数的基础上添加一些额外的功能。

使用 @ 语法只是一种更简洁的方式来应用装饰器:

@uppercase_decorator
def greet():
    return "hello, world!"

print(greet())  # 输出: HELLO, WORLD!

这种写法等同于:

def greet():
    return "hello, world!"

greet = uppercase_decorator(greet)

理解 @ 语法:

  1. @uppercase_decorator 放在函数定义之前。
  2. Python解释器会自动将下面定义的函数作为参数传递给装饰器函数。
  3. 装饰器函数的返回值会替换原始函数。

这种语法使得应用装饰器变得更加简洁和直观。

2.3 带参数的函数装饰器

到目前为止,我们只装饰了不带参数的函数。但在实际应用中,我们经常需要装饰带参数的函数。让我们来看看如何实现:

def logging_decorator(func):
    def wrapper(*args, **kwargs):
        # *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

@logging_decorator
def add(x, y):
    return x + y

print(add(3, 5))  # 调用装饰后的函数

输出:

Calling add with args: (3, 5), kwargs: {}
add returned 8
8

理解这段代码:

  1. logging_decorator 是一个装饰器函数,它接受一个函数 func 作为参数。
  2. 在装饰器内部,我们定义了 wrapper 函数。这个函数使用 *args 和 **kwargs 来捕获所有传递给原始函数的参数。
    • *args 是一个元组,包含所有位置参数。
    • **kwargs 是一个字典,包含所有关键字参数。
  3. wrapper 函数在调用原始函数前后添加了日志信息,打印出函数名、参数和返回值。
  4. wrapper 函数返回原始函数的结果,保持了函数的原有行为。
  5. 当我们使用 @logging_decorator 装饰 add 函数时,add 实际上被替换成了 wrapper 函数。
  6. 当我们调用 add(3, 5) 时,实际上是在调用 wrapper(3, 5)

这个例子展示了如何创建一个可以处理任意参数的装饰器。通过使用 *args 和 **kwargs,我们的装饰器可以适用于任何函数,无论它接受什么参数。

3.高级装饰器技巧

3.1 带参数的装饰器

有时我们需要能够自定义装饰器的行为。这可以通过创建一个返回装饰器的函数来实现:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

输出:

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

理解这段代码:

  1. repeat 是一个函数,它接受一个参数 times,并返回一个装饰器。
  2. decorator 是实际的装饰器函数,它接受要装饰的函数 func 作为参数。
  3. wrapper 是包装原始函数的内部函数。它会执行原始函数 times 次。
  4. 当我们使用 @repeat(3) 时,Python解释器首先调用 repeat(3),得到 decorator 函数。
  5. 然后,decorator 函数被用来装饰 greet 函数。
  6. 最终,greet 被替换成了 wrapper 函数。
  7. 当我们调用 greet("Alice") 时,实际上是在调用 wrapper("Alice"),它会执行原始的 greet 函数 3 次。

这个例子展示了如何创建一个可以接受参数的装饰器。这种技术允许我们在应用装饰器时自定义其行为,增加了装饰器的灵活性。

3.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"{self.func.__name__} has been called {self.num_calls} times")
        return self.func(*args, **kwargs)

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

say_hello()
say_hello()

输出:

say_hello has been called 1 times
Hello!
say_hello has been called 2 times
Hello!

理解这段代码:

  1. CountCalls 是一个类装饰器。它在初始化时接受要装饰的函数作为参数。
  2. __init__ 方法在装饰时被调用,它保存了被装饰的函数,并初始化了一个计数器。
  3. __call__ 方法使得类的实例可以像函数一样被调用。每次调用时,它会增加计数器,打印调用次数,然后执行原始函数。
  4. 当我们使用 @CountCalls 装饰 say_hello 函数时,say_hello 实际上被替换成了 CountCalls 类的一个实例。
  5. 当我们调用 say_hello() 时,实际上是在调用这个实例的 __call__ 方法。

类装饰器的优势在于它可以维护状态(在这个例子中是调用次数),并且可以提供更复杂的逻辑。它们特别适合于需要跟踪多次调用之间信息的场景。

3.3 保留函数元数据

当我们使用装饰器时,被装饰的函数会丢失一些元数据(如函数名、文档字符串等)。我们可以使用 functools.wraps 来解决这个问题:

from functools import wraps

def preserve_metadata(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """This is the wrapper function"""
        return func(*args, **kwargs)
    return wrapper

@preserve_metadata
def greet(name):
    """This function greets a person"""
    print(f"Hello, {name}!")

print(greet.__name__)  # 输出: greet
print(greet.__doc__)   # 输出: This function greets a person

理解这段代码:

  1. preserve_metadata 是一个装饰器函数。
  2. 我们使用 @wraps(func) 来装饰内部的 wrapper 函数。
  3. @wraps(func) 会将原始函数 func 的元数据(如名称、文档字符串等)复制到 wrapper 函数。
  4. 当我们查看 greet.__name__ 和 greet.__doc__ 时,我们得到的是原始 greet 函数的名称和文档字符串,而不是 wrapper 函数的。

使用 @wraps 是一个好习惯,它可以保持被装饰函数的元数据,这在调试和文档生成时特别有用。

3.4 可选参数的装饰器

有时我们希望装饰器既可以不带参数使用,也可以带参数使用。这里有一个技巧:

from functools import wraps, partial

def debug(func=None, *, prefix=''):
    if func is None:
        return partial(debug, prefix=prefix)
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"{prefix}Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@debug
def foo():
    pass

@debug(prefix='***')
def bar():
    pass

foo()  # 输出: Calling foo
bar()  # 输出: ***Calling bar

理解这段代码:

  1. debug 函数可以作为装饰器直接使用,也可以带参数使用。
  2. 当直接使用 @debug 时,func 参数会被赋值为被装饰的函数。
  3. 当使用 @debug(prefix='***') 时,func 参数为 Noneprefix 参数被设置。
  4. 如果 func 是 None,说明装饰器被调用时带有参数,我们返回一个偏函数 partial(debug, prefix=prefix)。这个偏函数会在下一步被调用,这时 func 参数会被赋值。
  5. @wraps(func) 用于保留原始函数

下面我们来探讨Python装饰器的高级用法和实际应用场景。

4.实用装饰器案例

4.1 计时器装饰器

在性能优化中,我们经常需要测量函数的执行时间。以下是一个计时器装饰器的实现:

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()  # 记录开始时间
        result = func(*args, **kwargs)  # 执行被装饰的函数
        end_time = time.time()  # 记录结束时间
        print(f"{func.__name__} ran in {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(2)  # 模拟耗时操作

slow_function()

输出:

slow_function ran in 2.0021 seconds

理解这段代码:

  1. timer 是一个装饰器函数,它不接受任何参数。
  2. 在 wrapper 函数中,我们记录函数开始执行的时间。
  3. 然后执行原始函数 func,并存储其结果。
  4. 记录函数结束执行的时间。
  5. 计算并打印函数的执行时间。
  6. 最后返回原始函数的结果,保持函数的原有行为。
  7. @wraps(func) 用于保留原始函数的元数据。

这个计时器装饰器可以帮助我们快速识别程序中的性能瓶颈。通过简单地在需要测试的函数上添加 @timer,我们就可以获得该函数的执行时间,而无需修改函数内部的代码。

4.2 重试装饰器

在处理网络请求或其他可能失败的操作时,我们可能需要多次尝试。以下是一个重试装饰器的实现:

import time
from functools import wraps

def retry(max_attempts, delay=1):
    def decorator(func):
        @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
                    print(f"Attempt {attempts} failed. Retrying in {delay} seconds...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise Exception("Random error")
    return "Success!"

print(unstable_function())

理解这段代码:

  1. retry 是一个可以接受参数的装饰器工厂函数。它返回实际的装饰器 decorator
  2. decorator 函数接受要装饰的函数 func 作为参数。
  3. 在 wrapper 函数中,我们实现了重试逻辑:
    • 我们最多尝试 max_attempts 次。
    • 如果函数执行成功,立即返回结果。
    • 如果函数抛出异常,我们捕获异常,增加尝试次数,并在下一次尝试前等待 delay 秒。
    • 如果达到最大尝试次数仍然失败,我们重新抛出最后一次的异常。
  4. @retry(max_attempts=3, delay=2) 使用了自定义的参数来装饰 unstable_function

这个重试装饰器在处理不稳定的操作时非常有用,比如网络请求、数据库操作等。它可以增加程序的鲁棒性,自动处理临时性的错误。

4.3 缓存装饰器

对于计算密集型的函数,特别是那些可能被多次调用的函数,我们可以使用缓存来提高性能。以下是一个简单的缓存装饰器:

from functools import wraps

def memoize(func):
    cache = {}
    @wraps(func)
    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(100))  # 快速计算大数列斐波那契数

理解这段代码:

  1. memoize 是一个装饰器函数,它不接受任何参数。
  2. 在装饰器内部,我们创建了一个 cache 字典来存储函数的结果。
  3. wrapper 函数首先检查函数的参数是否已经在缓存中。
  4. 如果参数在缓存中,直接返回缓存的结果。
  5. 如果参数不在缓存中,执行原始函数,将结果存入缓存,然后返回结果。
  6. 这个装饰器只能处理位置参数,不能处理关键字参数。

这个缓存装饰器特别适用于递归函数,如斐波那契数列计算。它可以显著提高函数的性能,特别是对于那些会重复计算相同输入的函数。

4.4 参数验证装饰器

在函数执行之前验证参数可以提高代码的健壮性。以下是一个参数类型验证的装饰器:

from functools import wraps

def validate_types(**expected_types):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 检查位置参数
            for arg, expected_type in zip(args, expected_types.values()):
                if not isinstance(arg, expected_type):
                    raise TypeError(f"Argument {arg} must be {expected_type}")
            # 检查关键字参数
            for name, expected_type in expected_types.items():
                if name in kwargs:
                    if not isinstance(kwargs[name], expected_type):
                        raise TypeError(f"Argument {name} must be {expected_type}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_types(x=int, y=int)
def add(x, y):
    return x + y

print(add(1, 2))  # 正常运行
# print(add(1, "2"))  # 引发 TypeError

理解这段代码:

  1. validate_types 是一个装饰器工厂函数,它接受键值对形式的参数类型说明。
  2. 它返回实际的装饰器函数 decorator
  3. wrapper 函数在调用原始函数之前进行参数类型检查:
    • 对于位置参数,它使用 zip 函数将参数与期望的类型配对。
    • 对于关键字参数,它遍历 expected_types 字典。
  4. 如果发现类型不匹配,抛出 TypeError
  5. 如果所有参数类型都正确,调用原始函数并返回结果。

这个参数验证装饰器可以帮助我们在函数执行前捕获类型错误,提高代码的可靠性和可维护性。

5.装饰器在实际项目中的应用

5.1 Web框架中的路由装饰器

在许多Web框架中,如Flask,装饰器被用来定义路由:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return "Welcome to the home page!"

@app.route('/about')
def about():
    return "This is the about page."

if __name__ == '__main__':
    app.run()

理解这段代码:

  1. @app.route('/') 是一个装饰器,它告诉Flask框架这个函数应该处理根URL ('/')的请求。
  2. 装饰器将URL路径与处理函数关联起来。
  3. 当有请求到达指定的URL时,Flask会调用相应的函数处理请求。

这种使用装饰器定义路由的方式使得代码更加简洁和直观,使开发者可以在函数定义的同时指定其URL路径。

5.2 身份验证装饰器

在Web应用中,我们经常需要检查用户是否已登录:

from functools import wraps
from flask import session, redirect, url_for

def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if 'user_id' not in session:
            return redirect(url_for('login'))
        return func(*args, **kwargs)
    return wrapper

@app.route('/dashboard')
@login_required
def dashboard():
    return "Welcome to your dashboard!"

理解这段代码:

  1. login_required 是一个装饰器函数。
  2. 在 wrapper 函数中,我们检查 session 中是否存在 'user_id'。
  3. 如果 'user_id' 不存在(即用户未登录),我们将用户重定向到登录页面。
  4. 如果用户已登录,我们允许原始函数执行。
  5. @login_required 装饰器可以应用于任何需要登录才能访问的路由处理函数。

这个身份验证装饰器可以方便地为多个路由添加登录检查,避免了在每个需要保护的路由中重复编写检查代码。

5.3 数据库会话管理

在使用ORM(如SQLAlchemy)时,我们可以使用装饰器来管理数据库会话:

from functools import wraps
from sqlalchemy.orm import Session
from your_db_config import engine

def with_session(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        session = Session(engine)
        try:
            result = func(session, *args, **kwargs)
            session.commit()
            return result
        except:
            session.rollback()
            raise
        finally:
            session.close()
    return wrapper

@with_session
def add_user(session, username, email):
    user = User(username=username, email=email)
    session.add(user)
    return user

理解这段代码:

  1. with_session 是一个装饰器函数。
  2. 在 wrapper 函数中,我们创建一个新的数据库会话。
  3. 我们在 try 块中执行原始函数,传入 session 作为第一个参数。
  4. 如果执行成功,我们提交事务。
  5. 如果发生异常,我们回滚事务并重新抛出异常。
  6. 无论执行是否成功,我们都确保在 finally 块中关闭会话。

这个数据库会话管理装饰器可以确保每个数据库操作都在一个独立的会话中执行,并正确处理事务的提交和回滚。它简化了数据库操作的错误处理和资源管理。

6.高级装饰器模式

6.1 装饰器类

除了使用函数来创建装饰器,我们还可以使用类来创建更复杂的装饰器:

class Profiler:
    def __init__(self, func):
        self.func = func
        self.calls = 0
        self.total_time = 0

    def __call__(self, *args, **kwargs):
        import time
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        self.calls += 1
        self.total_time += end_time - start_time
        print(f"{self.func.__name__} called {self.calls} times. "
              f"Average time: {self.total_time / self.calls:.5f} seconds")
        return result

@Profiler
def slow_function():
    import time
    time.sleep(1)

slow_function()
slow_function()

理解这段代码:

  1. Profiler 是一个装饰器类。
  2. __init__ 方法在装饰时被调用,保存了被装饰的函数,并初始化了调用次数和总时间。
  3. __call__ 方法使得类的实例可以像函数一样被调用。它在每次调用时记录执行时间,更新统计信息,并打印结果。
  4. 当我们使用 @Profiler 装饰 slow_function 时,slow_function 实际上被替换成了 Profiler 类的一个实例。

类装饰器的优势在于它可以更方便地维护状态(在这个例子中是调用次数和总时间),并且可以提供更复杂的逻辑。

6.2 带参数的类装饰器

我们还可以创建可以接受参数的类装饰器:

class RepeatN:
    def __init__(self, n):
        self.n = n

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            for _ in range(self.n):
                result = func(*args, **kwargs)
            return result
        return wrapper

@RepeatN(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

理解这段代码:

  1. RepeatN 是一个可以接受参数的类装饰器。
  2. __init__ 方法在创建装饰器实例时被调用,保存了重复次数 n
  3. __call__ 方法在装饰函数时被调用,它返回实际的装饰器函数 wrapper
  4. wrapper 函数实现了重复执行被装饰函数的逻辑。
  5. 当我们使用 @RepeatN(3) 时,首先创建了 RepeatN 类的一个实例,然后这个实例的 __call__ 方法被用

6.3 装饰器链

我们可以将多个装饰器应用到同一个函数上,这就形成了装饰器链:

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 greet():
    return "Hello, world!"

print(greet())  # 输出: <b><i>Hello, world!</i></b>

理解这段代码:

  1. 我们定义了两个简单的装饰器 bold 和 italic,它们分别在函数返回的字符串外围添加 HTML 标签。
  2. 我们使用 @bold 和 @italic 同时装饰 greet 函数。
  3. 装饰器的应用顺序是从下到上的,即最靠近函数定义的装饰器最先被应用。
  4. 这等同于 greet = bold(italic(greet))

装饰器链允许我们组合多个简单的装饰器来实现复杂的功能。但需要注意的是,过多的装饰器可能会影响函数的可读性和性能。

6.4 可调用对象作为装饰器

在 Python 中,任何实现了 __call__ 方法的对象都是可调用的,因此可以用作装饰器:

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

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"{self.prefix}: Calling {func.__name__}")
            return func(*args, **kwargs)
        return wrapper

@Logger("DEBUG")
def add(x, y):
    return x + y

print(add(3, 5))  # 输出: DEBUG: Calling add \n 8

理解这段代码:

  1. Logger 类实现了 __call__ 方法,使其实例成为可调用对象。
  2. __init__ 方法接受一个前缀参数,允许我们自定义日志消息。
  3. __call__ 方法接受被装饰的函数,并返回一个新的包装函数。
  4. 当我们使用 @Logger("DEBUG") 时,首先创建了 Logger 类的一个实例,然后这个实例被用作装饰器。

这种方法允许我们创建更灵活的装饰器,可以在装饰时传入参数来自定义装饰器的行为。

7.实际应用场景

7.1 API 速率限制

在构建 Web API 时,我们可能需要限制客户端的请求频率。以下是一个使用装饰器实现速率限制的例子:

import time
from functools import wraps

def rate_limit(max_calls, period):
    calls = []
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            now = time.time()
            calls[:] = [c for c in calls if c > now - period]
            if len(calls) >= max_calls:
                raise Exception("Rate limit exceeded")
            calls.append(now)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@rate_limit(max_calls=2, period=60)
def api_call():
    return "API response"

# 测试
for _ in range(3):
    try:
        print(api_call())
    except Exception as e:
        print(str(e))
    time.sleep(1)

理解这段代码:

  1. rate_limit 是一个装饰器工厂函数,它接受两个参数:max_calls(最大调用次数)和 period(时间周期,单位为秒)。
  2. 在装饰器内部,我们维护一个 calls 列表来记录函数调用的时间戳。
  3. 每次调用函数时,我们首先清理过期的时间戳,然后检查是否超过了速率限制。
  4. 如果超过限制,抛出异常;否则,记录本次调用并执行函数。

这个装饰器可以有效地限制 API 的调用频率,防止滥用和过度消耗服务器资源。

7.2 异步函数装饰器

在使用异步编程时,我们也可以为异步函数创建装饰器。以下是一个异步重试装饰器的例子:

import asyncio
from functools import wraps

def async_retry(max_attempts, delay=1):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return await func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Attempt {attempt + 1} failed. Retrying in {delay} seconds...")
                    await asyncio.sleep(delay)
        return wrapper
    return decorator

@async_retry(max_attempts=3, delay=2)
async def unstable_async_function():
    import random
    if random.random() < 0.7:
        raise Exception("Random error")
    return "Success!"

# 测试
async def main():
    try:
        result = await unstable_async_function()
        print(result)
    except Exception as e:
        print(f"Failed after all attempts: {str(e)}")

asyncio.run(main())

理解这段代码:

  1. async_retry 是一个装饰器工厂函数,返回一个可以装饰异步函数的装饰器。
  2. 装饰器的 wrapper 函数也是一个异步函数,使用 async def 定义。
  3. 在 wrapper 中,我们使用 await 关键字来调用被装饰的异步函数。
  4. 如果函数调用失败,我们使用 await asyncio.sleep(delay) 来异步等待before重试。
  5. 我们使用 asyncio.run() 来运行异步的 main 函数。

这个装饰器展示了如何在异步编程中使用装饰器,它可以为异步函数添加重试逻辑,提高异步操作的可靠性。

7.3 缓存异步函数结果

在处理耗时的异步操作时,缓存结果可以显著提高性能。以下是一个用于缓存异步函数结果的装饰器:

import asyncio
from functools import wraps

def async_lru_cache(maxsize=128):
    cache = {}
    
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            key = str(args) + str(kwargs)
            if key not in cache:
                if len(cache) >= maxsize:
                    # 简单的 LRU 策略:删除最早添加的项
                    cache.pop(next(iter(cache)))
                cache[key] = await func(*args, **kwargs)
            return cache[key]
        return wrapper
    return decorator

@async_lru_cache(maxsize=2)
async def fetch_data(url):
    print(f"Fetching data from {url}")
    await asyncio.sleep(2)  # 模拟网络延迟
    return f"Data from {url}"

async def main():
    urls = ["http://example.com", "http://example.org", "http://example.com"]
    for url in urls:
        result = await fetch_data(url)
        print(result)

asyncio.run(main())

理解这段代码:

  1. async_lru_cache 是一个装饰器工厂函数,它返回一个可以缓存异步函数结果的装饰器。
  2. 我们使用一个字典 cache 来存储函数调用的结果。
  3. 装饰器的 wrapper 函数检查缓存中是否已有结果。如果有,直接返回缓存的结果;如果没有,调用原函数并缓存结果。
  4. 我们实现了一个简单的 LRU(最近最少使用)策略:当缓存达到最大大小时,删除最早添加的项。
  5. 这个装饰器可以应用于异步函数,如示例中的 fetch_data 函数。

这个缓存装饰器可以显著提高需要重复调用的异步函数的性能,特别是在处理网络请求等耗时操作时。

8.装饰器的最佳实践和注意事项

8.1 保持简单

装饰器应该保持相对简单和专注。如果一个装饰器变得过于复杂,考虑将其拆分为多个更小的装饰器。

8.2 正确使用 functools.wraps

始终使用 @functools.wraps 来保留被装饰函数的元数据,这对于调试和文档生成很重要。

8.3 考虑性能影响

装饰器会增加函数调用的开销。对于频繁调用的小函数,过度使用装饰器可能会导致性能问题。

8.4 文档化装饰器

为你的装饰器编写清晰的文档,说明它们的用途、参数和任何副作用。

8.5 测试装饰器

编写单元测试来确保你的装饰器按预期工作,包括测试边界条件和异常情况。

结论

Python装饰器是一个强大的工具,可以以一种优雅和可重用的方式修改或增强函数和类的行为。从简单的日志记录到复杂的缓存机制,再到 Web 框架中的路由定义,装饰器在 Python 编程中无处不在。

通过深入理解装饰器的工作原理和常见模式,我们可以编写出更加简洁、可维护和高效的代码。然而,就像所有的编程技巧一样,装饰器也应该谨慎使用。过度使用装饰器可能会使代码变得难以理解和调试。

在实际应用中,装饰器可以帮助我们实现横切关注点(如日志、性能监控、访问控制等),而不会使主要业务逻辑变得复杂。通过合理使用装饰器,我们可以提高代码的模块化程度,增强其可读性和可维护性。

希望这篇深度指南能帮助您更好地理解和使用 Python 装饰器。无论您是刚开始学习 Python,还是已经是有经验的开发者,相信您都能从中获得一些新的见解和技巧。祝您编码愉快,充分发挥 Python 装饰器的强大功能!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值