You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python如何将冒号分隔的多行错误字符串规范转换为字典

问题描述

我在开发中使用的模块按如下方式定义API错误处理类:

class APIError(BetfairError):
    """
    Exception raised if error is found.
    """

    def __init__(
        self,
        response: Optional[dict],
        method: str = None,
        params: dict = None,
        exception: Exception = None,
    ):
        super(APIError, self).__init__(response, method, params, exception)
        self.response = response
        self.method = method
        self.params = params
        self.exception = exception

    def __str__(self):
        if self.response:
            error_data = self.response.get("error")
            return "%s \nParams: %s \nException: %s \nError: %s \nFull Response: %s" % (
                self.method,
                self.params,
                self.exception,
                error_data,
                self.response,
            )
        else:
            return "%s \nParams: %s \nException: %s" % (
                self.method,
                self.params,
                self.exception,
            )

该错误类输出的异常字符串为固定多行格式,每行均通过冒号分隔键名与值内容,值包含接口路径字符串、None、Python字典/列表字面量,格式示例如下:

betfairlightweight.exceptions.APIError: SportsAPING/v1.0/listMarketBook 
Params: {'marketIds': ['1.200293211'], 'priceProjection': {'priceData': ['EX_TRADED'], 'exBestOffersOverrides': {}, 'virtualise': True, 'rolloverStakes': False}} 
Exception: None 
Error: {'code': -32099, 'message': 'ANGX-0006', 'data': {'APINGException': {'requestUUID': '...........', 'errorCode': 'UNEXPECTED_ERROR', 'errorDetails': ''}, 'exceptionname': 'APINGException'}} 
Full Response: {'jsonrpc': '2.0', 'error': {'code': -32099, 'message': 'ANGX-0006', 'data': {'APINGException': {'requestUUID': '..........', 'errorCode': 'UNEXPECTED_ERROR', 'errorDetails': ''}, 'exceptionname': 'APINGException'}}, 'id': 1}

为了不受输出位置限制直接按键取值,我目前通过字符串替换拼接加eval的方式将该多行字符串转换为字典,实现代码如下:

# except Exception as e:
#     e.__str__()

e = """betfairlightweight.exceptions.APIError: SportsAPING/v1.0/listMarketBook 
Params: {'marketIds': ['1.200293211'], 'priceProjection': {'priceData': ['EX_TRADED'], 'exBestOffersOverrides': {}, 'virtualise': True, 'rolloverStakes': False}} 
Exception: None 
Error: {'code': -32099, 'message': 'ANGX-0006', 'data': {'APINGException': {'requestUUID': '...........', 'errorCode': 'UNEXPECTED_ERROR', 'errorDetails': ''}, 'exceptionname': 'APINGException'}} 
Full Response: {'jsonrpc': '2.0', 'error': {'code': -32099, 'message': 'ANGX-0006', 'data': {'APINGException': {'requestUUID': '..........', 'errorCode': 'UNEXPECTED_ERROR', 'errorDetails': ''}, 'exceptionname': 'APINGException'}}, 'id': 1}"""

fs = []
nfs = []
for row in e.split('\n'):
    fs.append(row[:row.find(':')] + ':')
    nfs.append('\'' + row[:row.find(':')] + '\'' + ':')
for x, y in zip(fs, nfs):
    e = e.replace(x, y)
for z in e.split('\n'):
    if (': {' not in z) and (': None' not in z):
        e = e.replace(z, z.replace(': ', ': \'').strip() + '\'')

for_dict = eval('{' + e.replace('\n', ',') + '}')

print(for_dict)

# Get SportsAPING/v1.0/listMarketBook value
print(for_dict['betfairlightweight.exceptions.APIError'])

# Get ABC-0025 value
print(for_dict['Error']['message'])

# Get 2.0 value
print(for_dict['Full Response']['jsonrpc'])

# Get ['EX_TRADED'] value
print(for_dict['Params']['priceProjection']['priceData'])

请问是否存在更符合Python编码规范的实现方式,或是现成的工具模块,可以更简洁、专业、安全地完成这类多行冒号分隔字符串到字典的转换?


实现方案

最优方案:直接访问异常对象属性,无需解析字符串

你当前的实现完全走了弯路:这个APIError类本身已经把所有需要的字段都作为公开实例属性存储,__str__方法只是为了方便日志打印做的格式化输出,根本不是给程序解析用的数据源。捕获异常后直接读取属性即可,零解析成本、零安全风险、性能最高:

try:
    # 你的接口调用逻辑
    client.call_api(...)
except APIError as e:
    # 直接读取属性,不需要处理任何字符串
    api_path = e.method
    request_params = e.params
    raw_exception = e.exception
    error_info = e.response.get("error") if e.response else None
    full_response = e.response

    # 直接按需取值即可
    print(api_path)  # 输出 SportsAPING/v1.0/listMarketBook
    print(error_info["message"])  # 输出 ANGX-0006
    print(full_response["jsonrpc"])  # 输出 2.0
    print(request_params["priceProjection"]["priceData"])  # 输出 ['EX_TRADED']

备选方案:仅能拿到异常字符串时,用安全解析替代eval

如果是从日志等场景拿到的只有格式化后的异常字符串、无法获取原始异常对象,绝对不要用eval——eval会执行字符串内的任意代码,只要异常内容被注入恶意代码就会直接触发安全问题。可以用Python标准库自带的ast.literal_eval做安全的字面量解析,这个方法只会解析字典、列表、字符串、数字、None、布尔值这类原生字面量,不会执行任意代码,实现逻辑也更简洁鲁棒:

import ast
from typing import Dict, Any

def parse_api_error(err_str: str) -> Dict[str, Any]:
    result = {}
    # 按行拆分,过滤空行
    lines = [line.strip() for line in err_str.splitlines() if line.strip()]
    for line in lines:
        # 只按第一个冒号拆分,避免值内部的冒号(比如字典里的字符串带冒号)导致拆分错误
        key, value_part = line.split(":", 1)
        key = key.strip()
        value_part = value_part.strip()

        if value_part == "None":
            value = None
        else:
            try:
                # 尝试解析为Python字面量(字典、列表、数字等)
                value = ast.literal_eval(value_part)
            except (SyntaxError, ValueError):
                # 解析失败说明是普通字符串,直接赋值
                value = value_part
        result[key] = value
    return result

这个实现比你当前的字符串替换逻辑鲁棒性更强,不会因为值内容里出现和键名重复的文本就替换出错,也没有任意代码执行的安全风险。

注意事项

  • 任何场景下都优先选第一种方案,解析格式化字符串是不得已的下策
  • 禁止用eval处理任何来源不可控的字符串,这是Python开发中明确的安全反模式

内容的提问来源于stack exchange,提问作者Digital Farmer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 20:21:18