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[index]
- result = value[key]
- 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 在内的系统异常,通常不推荐:至少写成 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[max(0, e.pos-20):e.pos+20]}')
- 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. 只有你能“做点什么”时才捕获,否则让异常继续向上传播。异常处理的本质是责任转移:函数无法完成职责时,通过异常告知调用者。
以上代码和规则可用于脚本、接口调用、配置读取、数据解析等场景。核心不是把所有错误吞掉,而是让可恢复错误有出路,让不可处理错误保留现场。 |