错误不过是“脾气不好”的值
当 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。