0

0

装饰器如何统一处理函数异常?实现重试机制的代码模板是怎样的?

絕刀狂花

絕刀狂花

发布时间:2025-06-28 18:12:02

|

270人浏览过

|

来源于php中文网

原创

装饰器通过捕获异常并分别处理不同类型的错误来提升代码的健壮性和可维护性。1. 装饰器本质上是语法糖,用于包裹目标函数并在其外部统一处理异常;2. 可以针对不同异常类型编写特定逻辑,例如对网络超时进行重试,而对参数错误直接抛出异常;3. 为避免装饰器嵌套导致性能下降,应简化内部逻辑、使用缓存或合并依赖装饰器;4. 装饰器还可用于权限校验、日志记录、性能监控、数据验证、缓存和事务管理等场景,从而扩展函数行为而不修改其本身。

装饰器如何统一处理函数异常?实现重试机制的代码模板是怎样的?

装饰器本质上是语法糖,利用它可以优雅地统一处理函数异常,顺便还能加上重试机制。这东西用好了,代码瞬间清爽不少。

装饰器如何统一处理函数异常?实现重试机制的代码模板是怎样的?

解决方案

装饰器如何统一处理函数异常?实现重试机制的代码模板是怎样的?

核心思路是,装饰器包裹目标函数,在装饰器内部捕获异常,然后根据需要进行处理,比如记录日志、重试、返回默认值等等。

import functools
import time
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def retry(max_retries=3, delay=1, logger=None):
    """
    一个通用的重试装饰器,可以自定义重试次数、延迟时间和日志记录器。
    """
    def decorator_retry(func):
        @functools.wraps(func)
        def wrapper_retry(*args, **kwargs):
            retries = 0
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    retries += 1
                    log_message = f"Function {func.__name__} failed with error: {e}, retrying ({retries}/{max_retries})..."
                    if logger:
                        logger.warning(log_message)
                    else:
                        logging.warning(log_message)
                    time.sleep(delay)
            # 如果所有重试都失败了,可以选择抛出异常或返回默认值
            raise Exception(f"Function {func.__name__} failed after {max_retries} retries.")

        return wrapper_retry
    return decorator_retry


def exception_handler(default_value=None, logger=None):
    """
    一个简单的异常处理装饰器,可以记录日志并返回默认值。
    """
    def decorator_exception_handler(func):
        @functools.wraps(func)
        def wrapper_exception_handler(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                log_message = f"Function {func.__name__} raised an exception: {e}"
                if logger:
                    logger.error(log_message)
                else:
                    logging.error(log_message)
                return default_value  # 返回默认值
        return wrapper_exception_handler
    return decorator_exception_handler


@retry(max_retries=3, delay=2, logger=logging)  # 使用retry装饰器,最多重试3次,每次间隔2秒
@exception_handler(default_value="Error", logger=logging) # 使用exception_handler装饰器,发生异常时返回"Error"
def might_fail(attempt):
    """
    一个可能失败的函数,用于演示。
    """
    print(f"Attempt {attempt}: Trying to execute might_fail...")
    if attempt < 2:
        raise ValueError(f"Simulated failure on attempt {attempt}")
    return f"Success on attempt {attempt}"


# 示例用法
if __name__ == "__main__":
    for i in range(3):
        try:
            result = might_fail(i)
            print(f"Result: {result}")
            break
        except Exception as e:
            print(f"Main block caught an exception: {e}")

装饰器如何处理不同类型的异常?

装饰器如何统一处理函数异常?实现重试机制的代码模板是怎样的?

装饰器可以针对不同类型的异常进行不同的处理。例如,可以针对网络请求超时进行重试,而对于参数错误则直接抛出异常。

磁力开创
磁力开创

快手推出的一站式AI视频生产平台

下载
import functools
import time
import logging
import requests

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def specific_exception_handler(func):
    """
    针对特定异常类型的处理装饰器。
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except requests.exceptions.RequestException as e:
            logging.error(f"Network error in {func.__name__}: {e}")
            # 可以选择重试网络请求,或者返回一个特定的错误值
            return None
        except ValueError as e:
            logging.error(f"Value error in {func.__name__}: {e}")
            raise  # 重新抛出ValueError,因为这通常是输入错误,不应该被忽略
        except Exception as e:
            logging.error(f"Unexpected error in {func.__name__}: {e}")
            return None  # 对于其他未知的异常,返回None
    return wrapper

@specific_exception_handler
def fetch_data(url):
    """
    模拟获取数据的函数,可能会抛出网络请求异常或ValueError。
    """
    print(f"Fetching data from {url}...")
    response = requests.get(url, timeout=5)
    response.raise_for_status()  # 如果响应状态码不是200,则抛出HTTPError
    return response.json()


if __name__ == "__main__":
    try:
        data = fetch_data("https://api.example.com/data")  # 假设这是一个有效的API endpoint
        print(f"Data: {data}")
    except Exception as e:
        print(f"Main block caught an exception: {e}")

    try:
        data = fetch_data("invalid_url")  # 模拟一个无效的URL,会抛出requests.exceptions.RequestException
        print(f"Data: {data}")
    except Exception as e:
        print(f"Main block caught an exception: {e}")

如何避免装饰器过度嵌套导致性能下降?

装饰器嵌套确实可能导致性能问题,尤其是当装饰器内部逻辑比较复杂时。解决这个问题,一方面要尽量简化装饰器内部的逻辑,另一方面可以考虑使用functools.lru_cache来缓存装饰器的结果,避免重复计算。此外,如果装饰器之间存在依赖关系,可以考虑将它们合并成一个装饰器。

除了重试和异常处理,装饰器还能用于哪些场景?

除了重试和异常处理,装饰器还能用于很多其他的场景,比如:

  • 权限校验: 检查用户是否有权限访问某个函数。
  • 日志记录: 记录函数的调用信息,包括参数和返回值。
  • 性能监控: 统计函数的执行时间。
  • 数据验证: 验证函数的输入参数是否符合要求。
  • 缓存: 缓存函数的返回值,避免重复计算。
  • 事务管理: 在函数执行前后开启和关闭数据库事务。

相关专题

更多
数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

350

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2075

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

347

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

255

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

323

2023.10.09

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

410

2023.10.16

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

399

2023.10.16

vb连接数据库的方法
vb连接数据库的方法

vb连接数据库的方法有使用ADO对象库、使用OLEDB数据提供程序、使用ODBC数据源等。详细介绍:1、使用ADO对象库方法,ADO是一种用于访问数据库的COM组件,可以通过ADO连接数据库并执行SQL语句。可以使用ADODB.Connection对象来建立与数据库的连接,然后使用ADODB.Recordset对象来执行查询和操作数据;2、使用OLEDB数据提供程序方法等等。

219

2023.10.19

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

12

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Django 教程
Django 教程

共28课时 | 3.3万人学习

MySQL 教程
MySQL 教程

共48课时 | 1.8万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号