R / Richie全部文章 ↑

Python · 5 分钟阅读

try / except 异常处理

Python 用 异常(Exception) 表示程序运行中的错误:除以零、找不到文件、网络超时、字典取不到 key……捕获异常能避免程序崩溃;抛出异常能让错误一路传到能被处理的地方。

本页是完整版。要看速查卡,看最后一节「速查表」。


1. 异常是怎么传播的?

当异常没有被捕获时,程序会立即停止运行并打印栈跟踪(traceback),告诉你出错的位置。栈跟踪在开发期非常有用——直接看最末尾的 xxxError 和出错的文件 + 行号就能定位。

Traceback (most recent call last):
  File "main.py", line 5, in <module>
    do_something()
  File "lib.py", line 12, in do_something
    return 1 / 0
ZeroDivisionError: division by zero

Python 内置异常被组织成层次结构,最顶层是 BaseException;通常你处理 Exception 即可。完整列表见 https://docs.python.org/3/library/exceptions.html。


2. 捕获异常:基本结构

try:
    ...
except <ErrorName>:
    ...
def get_age() -> int:
    while True:
        try:
            n = int(input("how old are you? "))
            return n
        except ValueError:
            print("Please enter an integer value.")

要点:

  • try 块里只放可能出错的代码,范围越具体越好。
  • 发生异常时,try 块剩余代码被跳过,立即跳到对应的 except。
  • 如果 try 块没抛异常,整个 except 块被跳过。

3. 捕获多种异常

def convert_int(s: str, base: int) -> int:
    try:
        return int(s, base)
    except (ValueError, TypeError) as e:    # 一并处理
        return "error"
def convert_int2(s, base):
    try:
        return int(s, base)
    except ValueError:
        return "value error"
    except TypeError:
        return "type error"

一个 try 可以有多个 except;精确捕获比裸 except 好得多。


4. 捕获所有异常(谨慎)

def convert_int3(s, base):
    try:
        return int(s, base)
    except Exception:           # 不要 except: 这种写法太宽
        return "error"

❌ except: 还会捕获 KeyboardInterrupt 和 SystemExit,会让 Ctrl-C 失效。除非真要拦所有,至少写成 except Exception。


5. else / finally

try:
    f = open("data.txt", encoding="utf-8")
    data = f.read()
except FileNotFoundError:
    print("file not found")
else:
    # 只在没有异常时执行
    print(f"read {len(data)} bytes")
finally:
    # 不管有没有异常都执行(清理资源)
    f.close()

finally 一定会执行——非常适合放 关闭文件、释放锁、关闭 socket 之类的清理代码。

更现代的写法是用 with:

with open("data.txt", encoding="utf-8") as f:
    data = f.read()
# 退出 with 时 f 自动 close,不需要 try/finally

6. 主动抛出异常:raise

def set_age(age: int) -> None:
    if not (0 < age <= 150):
        raise ValueError("age must be in (0, 150]")
    self._age = age

抛出原异常 + 上下文:

try:
    parse(blob)
except JSONDecodeError as e:
    raise ValueError("解析失败") from e

抑制链路(不保留原异常):

try:
    ...
except ValueError:
    raise RuntimeError("bad input") from None

想"不真正抛,只是把异常对象存起来":

try:
    ...
except ValueError as e:
    err = e   # 留待稍后处理

7. 自定义异常

class AppError(Exception):
    """所有本应用异常的基类。"""


class ConfigNotFound(AppError):
    pass


raise ConfigNotFound(f"missing config: {name}")

自定义异常继承 Exception(或具体子类);基类统一一个 能让上层 except AppError 一把兜住。


8. 异常链

try:
    int("abc")
except ValueError as e:
    raise RuntimeError("bad input") from e        # 显示链路
# RuntimeError: bad input
# The above exception was the direct cause of the following:
# ValueError: invalid literal for int() with base 10: 'abc'

用 from None 可以抑制链路。


9. 异常 vs. 校验

选什么 适用场景
异常 预期外 的情况(网络断、文件没、OOM)
显式校验 业务流程里正常分支(表单输入不合规)

业务流程里不要用异常走主路——异常是给"出错"用的,不是给"分支"用的。


10. 实战模式

10.1 资源管理用 with(更推荐)

with open("data.txt", encoding="utf-8") as f:
    data = f.read()
# 退出 with 时 f 自动 close,不需要 try/finally

10.2 重试:循环 + 退避

import time
import random


def with_retry(fn, *, tries: int = 3, base: float = 0.5):
    """最多重试 tries 次,指数退避 + 抖动。"""
    for i in range(tries):
        try:
            return fn()
        except Exception as e:
            if i == tries - 1:
                raise
            sleep = base * (2 ** i) + random.random() * 0.1
            print(f"retry {i+1}/{tries} after {sleep:.2f}s: {e}")
            time.sleep(sleep)

想做得更专业:用 tenacity / backoff 库。

10.3 "先看再拿"的安全字典访问

d = {"a": 1}
# 简单
val = d.get("b", default=0)
# 复杂:KeyError 当作"业务未命中",区别于真正的程序错误
try:
    val = d["b"]
except KeyError:
    val = default

11. 调试小技巧

  • traceback.print_exc():手动打印栈跟踪。
  • log.exception("msg"):日志 + 自动带 traceback。
  • pdb.pm():在异常点后调试。
  • sys.exc_info():拿到 (type, value, traceback),常用于钩子。

12. 常见误区

  • 裸 except::连 KeyboardInterrupt / SystemExit 都吃掉,调试极难。
  • 过于宽泛的 except Exception:吞掉了所有错误,让 bug 变沉默。
  • 用异常做流程控制:比如 try: return d[k] except KeyError: return default;多数情况 d.get(k, default) 更直接。
  • 在 except 里再抛不指定原因:丢失原上下文,调试时多走弯路。
  • try 范围太大:捕获了不该被捕获的代码,掩盖了真实问题。

速查表

程序一旦发生未捕获的异常,就会立即停止运行并打印栈跟踪——那是给开发者看的"出错位置"线索。

最小捕获

try:
    n = int(input("how old are you? "))
except ValueError:
    print("Please enter an integer value.")

捕获多种异常

def to_int(s, base):
    try:
        return int(s, base)
    except (ValueError, TypeError):
        return "error"

# 或者分别处理
def to_int2(s, base):
    try:
        return int(s, base)
    except ValueError:
        return "value error"
    except TypeError:
        return "type error"

清理操作:finally

def invert(x):
    try:
        return 1 / x
    except ZeroDivisionError:
        return "error"
    finally:
        print(f"invert({x}) done")

主动抛出

def set_age(age: int) -> None:
    if not (0 < age <= 150):
        raise ValueError("age must be in (0, 150]")

带上下文:

try:
    parse(blob)
except JSONDecodeError as e:
    raise ValueError("解析失败") from e     # 显式链路

抑制链路:

try:
    ...
except ValueError:
    raise RuntimeError("bad input") from None

模式一览

模式 说明
try / except 捕获异常
except (A, B) 一次捕获多种
except A as e: 拿到异常对象
else: try 块没出错时执行
finally: 不管有没有异常都执行
raise X(...) 主动抛出
raise X from e 抛出 + 保留原异常链路
raise X from None 抛出 + 隐藏原异常
with open(...) as f: 自动关闭(替代 try/finally)
except Exception as e: 兜底捕获
try / except 在循环里 "重试 N 次"模式

13. 小结

  • 异常是 Python 错误处理的核心机制;合理使用能写出健壮程序。
  • 优先用 with 管理资源;try / except / finally 是补充。
  • 精准捕获特定异常,别用裸 except:。
  • 自定义异常统一继承一个项目级基类,便于上层统一处理。
  • 重试、异常链、自定义异常是三大进阶武器。