跳至内容
Python

Python Try-Except 教程:最佳实践与真实案例

通过真实案例学习 Python 的 try-except、最佳实践与常见陷阱。编写更整洁、更可靠的错误处理代码。
Derrick Mwiti's photo
Derrick Mwiti
已更新 · 10分钟 阅读

使用 AI 探索

ChatGPTClaudePerplexity

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。

当 Python 代码在运行时遇到问题时,通常会抛出异常。如果不处理,异常会导致程序崩溃。不过,使用 try-except 代码块,您可以捕获异常,从容恢复,让应用继续运行。

本教程并不讲解异常的基础知识,我们已在 Python 异常与错误处理指南中覆盖。这里我们将深入实践 try-except:如何组织代码块、避免常见错误,并应用让代码在真实场景更可靠的最佳实践。

读完后,您不仅会理解 try-except 如何工作,更会掌握“Pythonic”的用法;编写清晰、易维护、可用于生产的错误处理代码。

如果您仍在学习 Python,我建议查看 Python 编程基础技能路径,帮助您构建所有关键技能。

为何聚焦 try-except?

在 Python 中,错误处理的根本在于设计能应对意外情况的程序。尽管有多种工具可用于处理错误,try-except 是 Python 方法论的支柱。

为何它如此重要?

  • 它体现了 Python 的哲学。 在 Python 中,常见风格是 EAFP;“先斩后奏”比“事前请示”更容易。与其事前检查一切(文件是否存在?输入是否有效?服务器是否可用?),不如直接尝试操作。如果失败,再捕获异常并处理。这样代码更短、更干净。
  • 它让代码在真实环境中保持运行。 用户输入会很乱、文件会丢失、API 会失败、网络会中断。借助 try-except,您可以让代码不至于在第一个问题处就崩溃,而是能智能地作出反应。
  • 它对新手与专业人士都足够灵活。 初学者可能用 try-except 捕获将输入转换为整数时的 ValueError。生产系统可能用它记录错误、重试失败操作,或抛出自定义异常以便更易调试。

这就是本文聚焦 try-except 的原因。掌握它,意味着从只在完美条件下才有效的小脚本,迈向健壮、可维护、可用于生产的软件。

Try‑Except 代码块的结构

最简单的形式:

try:
    risky_thing()
except SomeError:
    handle_it()

当 Python 在 try 块中遇到错误时,会跳转到匹配的 except 块,而不是让程序崩溃。

您也可以用 else 和 finally 子句扩展这个模式:

try:
    do_something()
except ValueError:
    print("Bad value!")
else:
    print("All good.")
finally:
    clean_up()

下面是一个基础可运行的示例:

try:
    x = int(input("Enter a number: "))
except ValueError:
    print("That wasn’t a number.")
else:
    print("You entered", x)
finally:
    print("Done.")

except 负责处理错误;else 仅在成功时运行;finally 无论如何都会运行,即使您按下 Ctrl‑C 或退出。

您可以通过我们的 Python 用户输入:处理、验证与最佳实践 教程,系统掌握从基础提示到高级验证与错误处理的用户输入处理要点。

处理多个异常

有时,一个 except 并不够。不同的代码路径会以不同方式失败,而用裸 except: 去兜底并无助益;它会隐藏 bug,让调试变成噩梦。

假设您要把某个值转换为整数再执行除法。下面是不要这样做的示例:

try:
    result = int(user_input) / 2
except:
    print("Something went wrong.")

这会捕获一切,包括您可能并不想隐藏的内容,比如变量名拼写错误,或指向真实问题的意外异常。

相反,要具体化:

try:
    result = int(user_input) / 2
except ValueError:
    print("That wasn't a number.")
except ZeroDivisionError:
    print("Division by zero?")

如果有几种异常需要用相同方式处理,您可以这样分组:

try:
    result = some_function()
except (TypeError, ValueError):
    print("Something was wrong with the data.")

这样,既能清楚表达可能出错的地方,又无需重复相同的处理代码块。

另外还有 except Exception,它比裸 except 要好,但仍然过宽。最好只针对您预期会发生的具体错误。

使用 else 与 finally 块

当 try 块中的代码成功运行且未抛出异常时,Python 会执行 else 块。

try:
    user_id = get_user_id()
except LookupError:
    print("User not found.")
else:
    print("Welcome, user", user_id)

这有助于将异常处理逻辑与“正常路径”分开,使代码更易读。

然后是 finally。它不关心发生了什么;无论如何都会运行。有异常?会运行。无异常?也会运行。按下 Ctrl‑C 程序中断?仍会运行。非常适合做清理工作:

try:
    f = open("data.txt")
    process(f)
except IOError:
    print("Couldn’t open file.")
finally:
    f.close()

务必注意,如果文件未成功打开,f 变量可能不存在,因此要小心。处理文件时,可以使用 Python 的上下文管理器(with open(...) as f:),更安全。

如果您想学习在 Python 中有效处理 KeyError 异常的关键技巧(如异常处理与预防),推荐阅读我们的 Python KeyError 异常及其修复方法 教程。

Python try-except 的最佳实践与常见陷阱

说实话:写好 try‑except 有点像艺术。写得好,能防止程序崩溃;写得差,会把 bug 深埋,直到应用崩掉、用户投诉,您才发现。

以下是值得养成的一些习惯:

让 try 块尽量精简。不要把上百行代码包在一个 try 里。那样很难定位问题来源。只包裹可能失败的代码:

# Good
try:
    value = int(data)
except ValueError:
    print("Couldn’t convert.")

# Bad
try:
    # Tons of unrelated logic
    value = int(data)
    do_more()
    something_else()
except ValueError:
    print("Huh?")

避免在操作前做条件检查,如果直接尝试并捕获错误更简单。Python 将此称为 EAFP(先斩后奏)。如果您先检查文件是否存在再打开,就引入了竞争条件。更好的做法:

try:
    with open("file.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("No file.")

这种模式避免了常见问题:文件可能在检查与打开之间被删除。直接尝试操作;若失败,再捕获失败。

同时,避免通过“捕获一切并什么也不做”来静默错误。不要这样:

try:
    something()
except:
    pass

除非您真的知道自己在做什么,否则这就是 bug 藏身并滋生的地方。若必须静默,请具体化:

try:
    something()
except TimeoutError:
    # okay to ignore in this case
    pass

并把错误记录到某处。完全吞掉意味着未来的您将毫无头绪。

内置异常 vs 自定义异常

Python 自带了大量异常,覆盖的场景超乎想象。您可能已经遇到过:

  • ValueError:类型正确但值无效,比如 int("hello")。
  • TypeError:对错误类型执行操作,比如把字符串与数字相加。
  • ZeroDivisionError:没错,就是除以零。
  • FileNotFoundError:尝试打开不存在的文件。
  • KeyError:字典缺少键。

这些很有用,对大多数脚本或应用已绰绰有余。但有时您会希望抛出更具描述性的异常,使之在您项目的语境内更合理,而不只是 Python 的语境。

比如,您在写一个处理在线订单的应用,当用户购买缺货商品时,希望抛出错误。您可以用 ValueError,但这太泛了,后续读代码的人看不出发生了什么。

这正是自定义异常大显身手的地方。

class OutOfStockError(Exception):
    pass

def check_inventory(product_id):
    if not in_stock(product_id):
        raise OutOfStockError(f"Product {product_id} is out of stock.")

通过创建自定义异常,您引入了更丰富的语义。同时也获得了更强的控制力;您可以只捕获这一特定情形:

try:
    check_inventory("shirt-001")
except OutOfStockError:
    print("Sorry, that item is sold out.")

虽是小改动,却让代码更易理解与维护,尤其在大型项目中。此外,它也利于日志与监控:自定义异常名比模糊的 ValueError 更易检索。

日志、重新抛出与异常链

调试时常有这样的瞬间:看到回溯(traceback),盯着看,却不知道为什么失败。这时就需要日志。它不仅是记录错误,更是为未来的您(或团队)留下线索,方便定位问题。

比如,您想在捕获异常后记录一下再继续:

import logging
logging.basicConfig(level=logging.ERROR)

try:
    do_something()
except ValueError as e:
    logging.error("Failed to do something: %s", e)
    raise

结尾的 raise 很重要——它在记录后重新抛出相同异常。没有它,您就把错误吞了。有时这样可以,但通常不行。

接下来是 raise from 小技巧。当您在处理一种错误时需要抛出另一种错误,又不想丢失原始异常,Python 允许将异常链起来:

try:
    connect_to_database()
except TimeoutError as e:
    raise ConnectionError("Database unavailable.") from e

这样回溯能讲全故事。您会看到新的 ConnectionError,也能看到导致它的 TimeoutError。

您也可以抑制原始错误(通常不建议)如下:

raise ConnectionError("Just this error, nothing else.") from None

除非有充分理由,保留完整异常链更有助于大家理解问题及其因果。

您可以在我们的 Python 日志记录教程 中学习日志记录基础。

真实世界的示例与用例

抽象地谈论异常处理是一回事,看到实际代码中的应用就会豁然开朗。

以用户输入为例。问问任何做过命令行工具或表单校验的人:用户能输入各种稀奇古怪的东西。您要一个数字?有人会输入“twelve”。或者粘贴电话号码。或者直接回车。这很常见。

与其写一长串“如果……怎么办”的检查,不如这样做:

while True:
    user_input = input("Enter a number: ")
    try:
        number = int(user_input)
        break
    except ValueError:
        print("Try again with a whole number.")

这段代码在遇到垃圾输入时不会慌张。它会提示用户重试,并循环直到输入有效。比为每个边界情况堆 if 语句干净多了。

再看一个:读取可能不存在的文件。

try:
    with open("config.json") as f:
        settings = f.read()
except FileNotFoundError:
    print("Missing config file. Using defaults.")
    settings = "{}"

无需先检查文件是否存在。直接尝试打开,如果失败,再继续处理。若您事先检查(os.path.exists()),文件可能会在检查与打开之间被他人删除。这就是竞争条件,调起来很痛苦。

网络请求更是异常的高发地带。您无法指望互联网总是稳定。服务器会宕机、连接会断、DNS 会失败。所以如果您这样做:

import requests

try:
    response = requests.get("https://example.com/data")
    response.raise_for_status()
except requests.exceptions.RequestException as e:
    print("Network problem:", e)

这个 RequestException 基类能方便地捕获 requests 可能抛出的几乎所有异常,从超时到错误响应。除非要区别对待,否则无需写十个不同的 except。

若您在编写自动化脚本或后端服务,用 try‑except 包裹关键逻辑,往往决定了是单个任务失败还是整个系统“黑屏”。您希望错误被记录、可恢复的任务被重试、不可恢复的任务能干净地停止,而不是让晦涩的堆栈轨迹在日志中没完没了地滚动。

通过我们的 Python 自动化:完整指南 教程,了解自动化的基础概念、关键库、数据处理、AI 增强与最佳实践。

Try-except 的高级用法

到现在,您已经见识到 try‑except 的灵活性。但灵活是一把双刃剑,很容易从有用滑向草率。以下是保持克制的方法。

捕获具体异常。若知道可能出错的类型,就把它写出来:

try:
    result = int(data)
except ValueError:
    # Only catches invalid numbers, not everything else under the sun

避免裸 except。除非处理非常特殊的情况,否则不要用。它会捕获 KeyboardInterrupt、SystemExit 等您大概率不想静默的异常。

在能让代码更清晰时使用 else 与 finally。不要为了用而用。如果“正常路径”的代码被埋在 try 里,也许该把它挪到 else。

让 try 块保持小而精。包得越多,就越难定位是哪一行出错。只包裹可能失败的部分。

在需要时记录错误,尤其在生产环境。即使程序没崩,知道“何时何地、什么失败了”,也能让后续调试轻松很多。

自定义异常不是必须,但很有帮助。若存在应用特定的问题,定义自己的错误类型。它们能让日志更易读、代码更自解释。

最后一点:不要把异常当作流程控制,除非别无更好办法。写“先试这个;失败了再那样”的逻辑很诱人,但如果这是高频路径,通常有更干净的实现方式。

结语

正如本文所述,异常处理不只是为了避免崩溃。它是写出能预见麻烦、从容应对、继续运行,或在必要时以合乎情理的方式停下的代码。这决定了用户是收到有帮助的提示,还是被丢回终端面对一长串难以阅读的回溯。

我强烈建议通过我们的 OOP in Python 课程中的 Catching Exceptions in Python 章节进行更多关于异常的动手学习。

Python try-except 常见问答

Python 中的错误与异常有什么区别?

“错误”(error)通常指阻止 Python 启动执行代码的问题,比如缺少冒号或关键字拼写错误,这些是语法错误。异常(exception)发生在代码运行过程中,比如试图除以零,或打开一个不存在的文件。异常可以被捕获并处理,从而避免程序崩溃。

在 Python 中使用裸 except 是否不好?

大多数情况下是的。裸 except 会捕获一切,包括您并不想捕获的内容,比如键盘中断或系统退出信号。这会让调试变得困难。最好捕获具体异常,例如 ValueError 或 FileNotFoundError,这样您清楚自己在处理什么。

何时应在 try-except 中使用 else?

当您希望仅在 try 块未发生异常时运行某段代码时使用 else。它有助于将成功路径的逻辑与错误处理逻辑分开,使代码更易读、更易维护。

如果我已经有 except,还需要 finally 做什么?

finally 无论是否发生错误都会运行。它非常适合做清理工作:关闭文件、释放资源、回滚事务等。即使发生错误或提前退出,finally 仍会执行。

我是否应总是使用 try-except 而不是先检查条件?

不总是,但很多时候最好直接 try 操作并在失败时 catch 错误。Python 开发者称之为 EAFP(“先斩后奏比事前许可更容易”)。这通常更快,也能避免某些 bug,特别是当检查与操作之间状态可能变化时(比如文件被删除)。


Derrick Mwiti's photo
Author
Derrick Mwiti
主题
Python

热门 DataCamp 课程

课程

Python 面向对象编程入门

3 小时
29.1K
掌握面向对象编程(OOP)的基础概念,构建自定义类和对象!
查看详情开始课程

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。
查看更多