异常捕获需按从具体到宽泛的顺序排列,否则子类异常会被父类异常提前捕获而失效;例如应先写except valueerror,再写except exception,且自定义异常和空except也须遵循此规则。
因为python的异常捕获是按顺序匹配的,一旦某个 except 块能处理当前异常,后续块就不再检查。如果宽泛的大异常(比如 exception)写在前面,它会提前“吃掉”所有子类异常,导致后面更具体的 except 块永远没机会执行。
异常继承关系决定匹配顺序
Python中异常类型是类,存在明确的继承体系。例如:
-
ValueError是Exception的子类 -
FileNotFoundError是OSError的子类,而OSError又是Exception的子类
当抛出 ValueError 时,except ValueError: 和 except Exception: 都能匹配。但解释器只取第一个匹配项——所以顺序错了,具体逻辑就失效了。
正确写法:从具体到宽泛
必须把子类异常放在前面,父类异常放在后面。例如:
try:
risky_operation()
except ValueError as e:
print("数值错误,可提示用户重输")
except FileNotFoundError as e:
print("文件不存在,请检查路径")
except OSError as e:
print("系统级IO问题,如权限或磁盘满")
except Exception as e:
print("其他未预期错误,记录日志并上报")
这样每种情况都能落到最贴切的处理分支里。
反例:顺序颠倒的后果
如果写成:
except Exception:
handle_all()
except ValueError: # 永远不会运行
handle_value_error()
那么 ValueError 会被前面的 Exception 捕获,后面这个专门处理它的分支形同虚设。调试时会发现“明明写了,却不生效”,其实是语法没错、逻辑断了。
实际开发中的常见疏漏
容易忽略的细节包括:
- 自定义异常也遵循继承规则,若继承自
Exception,就不能放在内置具体异常之前 - 用元组写多个异常(如
except (TypeError, ValueError):)时,整体仍算一个捕获单元,不改变与其他except的相对顺序 -
except:(空 except)等价于except BaseException:,它是所有异常的根,必须放在最后,否则会屏蔽KeyboardInterrupt等系统信号











