Menu

Python 异常处理:try、except、else、finally 和 raise

如何在 Python 中处理错误:try/except/finally、捕获特定异常、抛出自己的异常,以及什么时候该让错误继续向上传递。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

错误不过是“脾气不好”的值

当 Python 中出了问题,比如除以零、读取不存在的文件、解析一个格式不对的数字,运行时会创建一个异常对象,并开始沿着调用栈一层层往回退,直到有代码捕获它。如果没有任何代码捕获,程序就会终止并打印 traceback。

异常本身并不是坏东西。它是 Python 发出信号的方式:“这个操作我没法继续了,原因在这里。”你要做的是逐个判断:哪些异常你知道如何恢复,哪些应该让它继续上报。

基本结构

  • try: 开启一段可能出错的代码块。
  • except ValueError: 在 try 内部抛出这个特定异常时捕获它。
  • 如果没有发生异常,except 会被整个跳过。

输入 42 运行这段代码,一切正常。输入 hello 再运行,处理代码就会执行。

捕获特定异常

Python 的异常类型有一套层级结构。下面几个你会经常遇到:

  • ValueError:值在某方面不对(int("abc")、参数超出范围)。
  • TypeError:用错了类型("hi" + 3)。
  • KeyError:字典中找不到某个键。
  • IndexError:序列索引超出范围。
  • FileNotFoundError:文件不存在。
  • ZeroDivisionError:试图除以零。
  • AttributeError:对象没有所请求的属性。

捕获你知道如何处理的那个特定异常:

把一个元组传给 except,就能在一个子句里捕获多种异常:

注意 as e。它把异常对象绑定到 e,这样你就可以查看它的消息或属性。

不要什么都捕获

光秃秃的 except: 会捕获一切,包括 KeyboardInterrupt(你按下的 Ctrl-C)和系统级的退出。不要这样用。

except Exception: 稍好一点,但依然危险:它会吞掉你没预料到的 bug,掩盖问题的真正来源:

# Don't do this without a really good reason.
try:
    do_something()
except Exception:
    pass

正确的做法几乎总是只捕获你知道如何恢复的特定异常。如果一个你没预料到的异常一路传到了程序顶层,traceback 会准确告诉你哪里出了错。这是一项功能,而不是 bug。

else 和 finally

try 语句还有两个可选子句:

  • else 在 try 块没有抛出异常、顺利执行完时运行。
  • finally 无论是否发生异常都会运行。

else 是放“只在成功时执行”的代码最清晰的地方:try 块里只应放真正可能失败的那部分代码。finally 用于即使 try 失败也必须执行的清理工作:关闭资源、释放锁、恢复状态。

自己抛出异常

在你自己的代码中用 raise 发出错误信号:

选择一个符合出错情况的异常类型。优先使用内置异常,比如 ValueError、TypeError、FileNotFoundError,然后才考虑定义自己的类。

自定义异常

当内置异常无法准确表达含义时,就定义一个自定义异常:

继承 Exception(或一个更具体的内置异常),再给这个类写一个文档字符串,通常就够了。自定义异常让调用者可以只捕获对其业务有意义的那个错误。

raise ... from ...:异常链

当一个异常引发了另一个异常时,要保留这条链:

from e 会把原始错误附加上去。打印 traceback 时,Python 会同时显示两者:浮出水面的 ConfigError 和引发它的 FileNotFoundError。调试时,这样的线索非常宝贵。

上下文管理器:更简洁的清理方式

finally 没问题,但对于文件这类资源,上下文管理器(with 所用的机制)几乎总是更好的选择:

# finally version
f = open("data.txt")
try:
    data = f.read()
finally:
    f.close()

# with version
with open("data.txt") as f:
    data = f.read()

两种写法都是安全的。with 写法更短,而且会自动生效。只有当你做的事情标准库还没有用上下文管理器封装时,才需要用 finally。

什么时候不该捕获

捕获异常是一个决定:你在说“这个我能处理”。如果你处理不了,就让异常继续向上传递。像下面这样的代码几乎总是错的:

try:
    do_work()
except Exception:
    pass  # silently ignore everything

把错误悄悄吞掉会让 bug 变得无影无踪。与其在不一致的状态下勉强运行,不如大声地崩溃。

小结

  • try/except 让你处理那些可以恢复的错误。
  • 捕获特定异常,不要笼统地捕获 Exception。
  • raise 在你自己的代码中发出错误信号。
  • with 块可以取代大多数 finally 清理代码。
  • 拿不准时,就让异常继续向上传递。

下一篇:Python 最常抛出的几种错误一览,包括 KeyError、ValueError、ModuleNotFoundError 等,以及能快速修复它们的调试习惯。

常见问题

Python 中怎么处理错误?

把可能出错的代码放进 try 块,再用 except 块捕获特定的异常:try: risky() except ValueError: ...。可选的 else 在没有发生异常时运行;finally 无论如何都会运行,用来做清理工作。

为了保险,应该直接捕获 Exception 吗?

不应该。光秃秃的 except: 或 except Exception: 会把你没预料到的 bug 藏起来。只捕获你知道如何恢复的特定异常,其余的让它继续向上传递,这样你才能看到真正的问题。

raise 和 raise from 有什么区别?

raise NewError(...) 抛出一个新的异常。raise NewError(...) from original 会把原来的异常作为原因附加上去,Python 会在 traceback 中显示它。当一个底层错误引发了你想向上报告的高层错误时,就用 from。

Coddy 编程语言插图

和 Coddy 一起学编程

开始