查看: 299|回复: 0

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

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
Python 的 try-except 是运行时错误的“安全气囊”:异常不处理,程序会中断;把可能出错的代码放进 try,并用 except 捕获指定异常,就能决定继续执行还是返回默认值。下面按语法、捕获顺序、实战重试和 JSON 解析几个方面整理。

一、基本语法与最小示例
try 块放可能出错的代码,except 指定要匹配的异常类型。除零示例:
  1. try:
  2.     result = 1 / 0
  3.     print(f'结果: {result}')
  4. except ZeroDivisionError:
  5.     print('错误: 不能除以零!')
  6.     result = None
  7. print('程序继续执行...')
  8. print(f'result = {result}')
复制代码
这里 ZeroDivisionError 被捕获后,后面的代码继续执行,result 被赋值为 None。若没有 try-except,程序会在 1 / 0 处崩溃。

安全获取数字输入时,可把 input 和 float 转换放在 try 中,循环直到成功:
  1. def get_number(prompt='请输入数字: '):
  2.     while True:
  3.         try:
  4.             value = input(prompt)
  5.             return float(value)
  6.         except ValueError:
  7.             print(f'{value}不是一个有效的数字,请重新输入')
复制代码
ValueError 对应 float 转换失败。注意 value 在输入后才有值,因此 except 中可引用它。

读取配置文件时,文件不存在和 JSON 格式错误可以分别处理:
  1. import json
  2. def read_config(filepath):
  3.     try:
  4.         with open(filepath, 'r', encoding='utf-8') as f:
  5.             return json.load(f)
  6.     except FileNotFoundError:
  7.         print(f'配置文件{filepath}不存在,使用默认配置')
  8.         return {'debug': False, 'host': 'localhost'}
  9.     except json.JSONDecodeError:
  10.         print(f'配置文件{filepath}格式错误,使用默认配置')
  11.         return {'debug': False, 'host': 'localhost'}
复制代码

二、一次捕获多种异常
一个 try 可以跟多个 except,每种异常走不同分支。下面的 safe_operation 依次可能遇到 IndexError、KeyError、TypeError:
  1. def safe_operation(data, index, key):
  2.     try:
  3.         value = data[index]
  4.         result = value[key]
  5.         return result
  6.     except IndexError:
  7.         print(f'索引{index}超出范围(数据长度={len(data)})')
  8.         return None
  9.     except KeyError:
  10.         print(f'键{key}不存在')
  11.         return None
  12.     except TypeError as e:
  13.         print(f'类型错误: {e}(data[{index}]不是字典)')
  14.         return None
  15. data = [{'a': 1}, {'b': 2}]
  16. print(safe_operation(data, 1, 'b'))
  17. print(safe_operation(data, 5, 'a'))
  18. print(safe_operation(data, 0, 'z'))
  19. print(safe_operation(data, 0, 'a'))
复制代码
调用结果依次为 2、索引越界返回 None、键不存在返回 None、正常返回 1。

三、捕获顺序:子类必须放在父类之前
except 按书写顺序匹配,一旦匹配成功,后面的 except 不再执行。因此更具体的异常要放在更宽泛的异常之前:
  1. def good_order():
  2.     try:
  3.         result = 1 / 0
  4.     except ZeroDivisionError:
  5.         print('除以零错误')
  6.     except ArithmeticError:
  7.         print('算术错误')
  8.     except Exception:
  9.         print('其他异常')
  10. good_order()
复制代码
如果先写 except Exception,再写 except ZeroDivisionError,后者永远匹配不到。因为 ZeroDivisionError 是 ArithmeticError 等父类的子类。

裸 except 会捕获包括 KeyboardInterrupt 在内的系统异常,通常不推荐:
  1. try:
  2.     pass
  3. except:
  4.     pass
复制代码
至少写成 except Exception as e,并记录日志或回滚;更好的做法是只捕获你能处理的异常,例如:
  1. try:
  2.     pass
  3. except (ValueError, TypeError) as e:
  4.     print(f'数据错误: {e}')
  5. except IOError as e:
  6.     print(f'I/O错误: {e}')
复制代码

四、实战:API 调用重试
网络调用可能因 ConnectionError 失败,适合有限次重试。下例参数 max_retries=3、delay=1;ConnectionError 会等待后重试,ValueError 表示参数错误,直接 raise 不重试;全部失败后抛出 ConnectionError:
  1. import time
  2. import random
  3. def call_api_with_retry(url, max_retries=3, delay=1):
  4.     last_error = None
  5.     for attempt in range(max_retries):
  6.         try:
  7.             if random.random() < 0.7:
  8.                 raise ConnectionError(f'连接超时 (尝试{attempt+1})')
  9.             return f'API响应: 数据来自{url}'
  10.         except ConnectionError as e:
  11.             print(f'⚠ {e},{delay}秒后重试...')
  12.             last_error = e
  13.             time.sleep(delay)
  14.         except ValueError as e:
  15.             print(f'✗ 参数错误: {e}')
  16.             raise
  17.     raise ConnectionError(f'API调用失败(重试{max_retries}次后): {last_error}')
  18. try:
  19.     result = call_api_with_retry('https://api.example.com/data')
  20.     print(f'✓ {result}')
  21. except ConnectionError as e:
  22.     print(f'✗ 最终失败: {e}')
复制代码
重试适合可恢复错误;参数错误重试无意义,所以 ValueError 直接向上传播。

五、实战:安全解析 JSON
json.loads 可能抛 JSONDecodeError,输入为 None 或空字符串也应处理。下例返回 default,并利用异常对象的 lineno、colno、pos 打印定位信息:
  1. import json
  2. def safe_json_parse(json_string, default=None):
  3.     if default is None:
  4.         default = {}
  5.     if json_string is None:
  6.         print('输入为None,返回默认值')
  7.         return default
  8.     if not json_string.strip():
  9.         print('输入为空字符串,返回默认值')
  10.         return default
  11.     try:
  12.         return json.loads(json_string)
  13.     except json.JSONDecodeError as e:
  14.         print(f'JSON解析错误: {e}')
  15.         print(f' 位置: 第{e.lineno}行, 第{e.colno}列')
  16.         print(f' 内容片段: {json_string[max(0, e.pos-20):e.pos+20]}')
  17.         return default
  18.     except TypeError as e:
  19.         print(f'类型错误: {e}')
  20.         return default
  21. print(safe_json_parse('{"name": "张三", "age": 25}'))
  22. print(safe_json_parse('{invalid json}'))
  23. print(safe_json_parse(None))
复制代码
合法 JSON 返回解析后的对象;非法 JSON 返回 default,并输出错误位置;None 直接返回 default。

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-8 15:56 , Processed in 0.032832 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部