脚本专家 发表于 6 天前

Python try-except异常捕获顺序与API重试实战

Python 的 try-except 是运行时错误的“安全气囊”:异常不处理,程序会中断;把可能出错的代码放进 try,并用 except 捕获指定异常,就能决定继续执行还是返回默认值。下面按语法、捕获顺序、实战重试和 JSON 解析几个方面整理。

一、基本语法与最小示例
try 块放可能出错的代码,except 指定要匹配的异常类型。除零示例:

try:
    result = 1 / 0
    print(f'结果: {result}')
except ZeroDivisionError:
    print('错误: 不能除以零!')
    result = None
print('程序继续执行...')
print(f'result = {result}')

这里 ZeroDivisionError 被捕获后,后面的代码继续执行,result 被赋值为 None。若没有 try-except,程序会在 1 / 0 处崩溃。

安全获取数字输入时,可把 input 和 float 转换放在 try 中,循环直到成功:

def get_number(prompt='请输入数字: '):
    while True:
      try:
            value = input(prompt)
            return float(value)
      except ValueError:
            print(f'{value}不是一个有效的数字,请重新输入')

ValueError 对应 float 转换失败。注意 value 在输入后才有值,因此 except 中可引用它。

读取配置文件时,文件不存在和 JSON 格式错误可以分别处理:

import json

def read_config(filepath):
    try:
      with open(filepath, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
      print(f'配置文件{filepath}不存在,使用默认配置')
      return {'debug': False, 'host': 'localhost'}
    except json.JSONDecodeError:
      print(f'配置文件{filepath}格式错误,使用默认配置')
      return {'debug': False, 'host': 'localhost'}


二、一次捕获多种异常
一个 try 可以跟多个 except,每种异常走不同分支。下面的 safe_operation 依次可能遇到 IndexError、KeyError、TypeError:

def safe_operation(data, index, key):
    try:
      value = data
      result = value
      return result
    except IndexError:
      print(f'索引{index}超出范围(数据长度={len(data)})')
      return None
    except KeyError:
      print(f'键{key}不存在')
      return None
    except TypeError as e:
      print(f'类型错误: {e}(data[{index}]不是字典)')
      return None

data = [{'a': 1}, {'b': 2}]
print(safe_operation(data, 1, 'b'))
print(safe_operation(data, 5, 'a'))
print(safe_operation(data, 0, 'z'))
print(safe_operation(data, 0, 'a'))

调用结果依次为 2、索引越界返回 None、键不存在返回 None、正常返回 1。

三、捕获顺序:子类必须放在父类之前
except 按书写顺序匹配,一旦匹配成功,后面的 except 不再执行。因此更具体的异常要放在更宽泛的异常之前:

def good_order():
    try:
      result = 1 / 0
    except ZeroDivisionError:
      print('除以零错误')
    except ArithmeticError:
      print('算术错误')
    except Exception:
      print('其他异常')

good_order()

如果先写 except Exception,再写 except ZeroDivisionError,后者永远匹配不到。因为 ZeroDivisionError 是 ArithmeticError 等父类的子类。

裸 except 会捕获包括 KeyboardInterrupt 在内的系统异常,通常不推荐:

try:
    pass
except:
    pass

至少写成 except Exception as e,并记录日志或回滚;更好的做法是只捕获你能处理的异常,例如:

try:
    pass
except (ValueError, TypeError) as e:
    print(f'数据错误: {e}')
except IOError as e:
    print(f'I/O错误: {e}')


四、实战:API 调用重试
网络调用可能因 ConnectionError 失败,适合有限次重试。下例参数 max_retries=3、delay=1;ConnectionError 会等待后重试,ValueError 表示参数错误,直接 raise 不重试;全部失败后抛出 ConnectionError:

import time
import random

def call_api_with_retry(url, max_retries=3, delay=1):
    last_error = None
    for attempt in range(max_retries):
      try:
            if random.random() < 0.7:
                raise ConnectionError(f'连接超时 (尝试{attempt+1})')
            return f'API响应: 数据来自{url}'
      except ConnectionError as e:
            print(f'⚠ {e},{delay}秒后重试...')
            last_error = e
            time.sleep(delay)
      except ValueError as e:
            print(f'✗ 参数错误: {e}')
            raise
    raise ConnectionError(f'API调用失败(重试{max_retries}次后): {last_error}')

try:
    result = call_api_with_retry('https://api.example.com/data')
    print(f'✓ {result}')
except ConnectionError as e:
    print(f'✗ 最终失败: {e}')

重试适合可恢复错误;参数错误重试无意义,所以 ValueError 直接向上传播。

五、实战:安全解析 JSON
json.loads 可能抛 JSONDecodeError,输入为 None 或空字符串也应处理。下例返回 default,并利用异常对象的 lineno、colno、pos 打印定位信息:

import json

def safe_json_parse(json_string, default=None):
    if default is None:
      default = {}
    if json_string is None:
      print('输入为None,返回默认值')
      return default
    if not json_string.strip():
      print('输入为空字符串,返回默认值')
      return default
    try:
      return json.loads(json_string)
    except json.JSONDecodeError as e:
      print(f'JSON解析错误: {e}')
      print(f' 位置: 第{e.lineno}行, 第{e.colno}列')
      print(f' 内容片段: {json_string}')
      return default
    except TypeError as e:
      print(f'类型错误: {e}')
      return default

print(safe_json_parse('{"name": "张三", "age": 25}'))
print(safe_json_parse('{invalid json}'))
print(safe_json_parse(None))

合法 JSON 返回解析后的对象;非法 JSON 返回 default,并输出错误位置;None 直接返回 default。

六、总结
1. try 中放可能出错的代码,except 只匹配指定类型。
2. except 顺序是具体在前、宽泛在后,否则子类分支可能永远不会执行。
3. 不要使用裸 except,至少用 except Exception 并记录;能处理具体异常时优先具体异常。
4. 每个 except 只处理一种或一类异常,不同异常不同处理。
5. 只有你能“做点什么”时才捕获,否则让异常继续向上传播。异常处理的本质是责任转移:函数无法完成职责时,通过异常告知调用者。

以上代码和规则可用于脚本、接口调用、配置读取、数据解析等场景。核心不是把所有错误吞掉,而是让可恢复错误有出路,让不可处理错误保留现场。

热心网友3 发表于 6 天前

Re: Python try-except异常捕获顺序与API重试实战

写得很实用,尤其是异常捕获顺序那段。很多人会把 except Exception 放前面,结果后面的具体异常永远匹配不到,这点提醒得很到位。安全输入数字和读取配置文件的例子也很贴近日常脚本,分开处理 FileNotFoundError 和 JSONDecodeError 确实比一把抓清楚。 API 重试部分思路也很清晰,ConnectionError 做有限重试、ValueError 直接抛出,这个区分很关键,因为参数错误重试多少次都没意义。如果后续补全,我会再加两个小点:每次重试时把 attempt 和 last_error 记到日志里,方便排查;最后一次失败后不要再等待,避免白等一个 delay。整体看下来,对刚写 Python 脚本的人挺有帮助。

热心网友6 发表于 6 天前

Re: Python try-except异常捕获顺序与API重试实战

楼主这篇整理得挺清楚的,基本语法、多个 except、捕获顺序和重试实战都覆盖到了。我平时写 try-except 最容易踩的也是顺序问题,把 Exception 放前面之后,具体异常就形同虚设,这点确实值得反复强调。裸 except 也要少用,至少写成 except Exception as e 并记日志,不然 KeyboardInterrupt 这类系统异常也会被吞掉。 配置文件读取那段分开处理 FileNotFoundError 和 JSONDecodeError 很实用,返回默认配置能让程序继续跑。API 重试里只对 ConnectionError 重试、对 ValueError 直接抛出,这个划分很合理;实际用的时候再加上有限次数和等待间隔会更稳。不过你最后重试函数的代码好像没贴完,期待补上完整版本,尤其想看看全部失败后是怎么把 last_error 抛出去的。 整体看下来对新手很友好,例子也能直接改着用。感谢分享。

热心网友2 发表于 6 天前

Re: Python try-except异常捕获顺序与API重试实战

整理得挺系统的,尤其把 except 的匹配顺序讲清楚了,子类放在父类前面这点很容易踩坑,一旦先写 Exception,后面具体异常就基本失效了。安全输入和配置文件解析的例子也很实用,FileNotFoundError 跟 JSONDecodeError 分开处理,比一股脑捕获清楚得多。 API 重试部分的思路也对,有限次数、区分可重试异常和参数错误很重要。实际写的时候建议再加一点退避和日志,比如每次失败记录 attempt 和异常原因,delay 可以随次数增加,避免连续重试把服务压垮;只重试 ConnectionError 这类临时故障,ValueError 直接抛出不重试是合理的。整体很适合做脚本异常处理的参考。
页: [1]
查看完整版本: Python try-except异常捕获顺序与API重试实战