Python异常处理语法:try-except使用实例
详细讲解Python中try-except异常处理语法的使用方法,通过多个实例演示如何捕获、处理和自定义异常,帮助你写出更健壮的Python代码。
你正在写一个读取文件的脚本,程序运行到一半突然报错 FileNotFoundError,整个进程直接退出——这是Python开发中最常遇到的场景之一。try-except 就是Python提供的异常处理机制,它允许你把可能出错的代码包裹起来,在错误发生时执行你预设的处理逻辑,而不是让程序直接崩溃。本文通过具体实例,完整讲解 try-except 的语法结构、常见用法和实际注意事项。
异常是什么
Python在运行期间遇到无法正常执行的情况时,会产生一个异常对象。如果这个异常没有被处理,程序就会终止并打印回溯信息(Traceback)。常见的异常类型包括:
| 异常类型 | 典型触发场景 |
|---|---|
ZeroDivisionError | 除数为零 |
FileNotFoundError | 打开的文件不存在 |
ValueError | 函数收到类型正确但值不合法的参数 |
TypeError | 对不支持的类型执行操作 |
KeyError | 字典中不存在指定的键 |
IndexError | 列表索引超出范围 |
所有内置异常都继承自 BaseException,大多数我们需要捕获的异常继承自 Exception。了解这个继承关系,有助于理解后面按类型捕获异常的写法。
try-except 基本语法
try-except 的最基本形式是:把可能出错的代码放在 try 块中,把出错后要执行的代码放在 except 块中。
try:
result = 10 / 0
except ZeroDivisionError:
print("除数不能为零")
运行这段代码,输出如下:
# 示例输出
除数不能为零
程序不会崩溃,而是执行了 except 中的打印语句。这里的 ZeroDivisionError 指定了要捕获的异常类型。如果 try 块中抛出的异常类型与 except 后面写的不匹配,异常仍然不会被捕获,程序照样会终止。
捕获多种异常
实际代码中,一段逻辑可能抛出不同类型的异常。你可以写多个 except 分支分别处理,也可以在一个 except 中用元组同时捕获多种类型。
多个 except 分支
data = {"name": "Alice"}
try:
value = data["age"] # 可能 KeyError
number = int(value) # 可能 ValueError
except KeyError as e:
print(f"字典中缺少键: {e}")
except ValueError as e:
print(f"值转换失败: {e}")
运行这段代码会输出:
# 示例输出
字典中缺少键: 'age'
Python会按照 except 分支的书写顺序从上到下匹配。一旦某个分支匹配成功,就执行该分支并跳过后续的 except 块。as e 把异常对象绑定到变量 e,可以用来获取具体的错误信息。
用元组捕获多种异常
如果几种异常的处理方式相同,可以合并成一个分支:
try:
result = int("abc")
except (ValueError, TypeError) as e:
print(f"输入数据有误: {e}")
FileNotFoundError 是 OSError 的子类,如果 except OSError 写在前面,FileNotFoundError 的分支永远不会被执行。完整结构:try-except-else-finally
try 语句的完整形式包含四个部分,每个部分有明确的执行时机:
try:
f = open("config.txt", "r")
content = f.read()
except FileNotFoundError:
print("配置文件不存在,将使用默认配置")
content = "default=true"
else:
print(f"成功读取配置,共 {len(content)} 个字符")
finally:
print("无论是否出错,这里都会执行")
# 如果文件打开成功,在这里关闭
if 'f' in dir():
f.close()
- try:放置可能抛出异常的代码。
- except:当 try 块中发生指定异常时执行。
- else:只在 try 块没有发生任何异常时执行。适合放"成功后才需要做"的逻辑,让 try 块保持简短。
- finally:无论是否发生异常都会执行。通常用来释放资源(关闭文件、断开连接等)。
获取异常信息
通过 as 关键字把异常对象绑定到变量后,可以获取详细的错误描述。结合 traceback 模块,还能拿到完整的调用栈信息,这在记录日志时非常有用。
import traceback
def divide(a, b):
return a / b
try:
result = divide(5, 0)
except ZeroDivisionError as e:
print(f"异常类型: {type(e).__name__}")
print(f"异常信息: {e}")
print("调用栈:")
traceback.print_exc()
示例输出:
# 示例输出
异常类型: ZeroDivisionError
异常信息: division by zero
调用栈:
Traceback (most recent call last):
File "demo.py", line 6, in <module>
result = divide(5, 0)
File "demo.py", line 3, in divide
return a / b
ZeroDivisionError: division by zero
在正式项目中,通常会用 logging.exception() 代替 print,它会自动把调用栈写入日志文件。
实用场景:用户输入验证
下面是一个实际的例子——循环接收用户输入,直到拿到合法的整数为止:
def get_positive_integer(prompt_text):
"""反复提示用户,直到输入一个正整数"""
while True:
try:
value = int(input(prompt_text))
if value <= 0:
raise ValueError("需要正整数")
return value
except ValueError as e:
print(f"输入无效: {e},请重新输入")
# 调用示例
# age = get_positive_integer("请输入年龄: ")
这个例子展示了两个要点:第一,try-except 可以和循环配合,实现"重试到成功"的模式;第二,你可以用 raise 手动抛出异常来统一处理不同类型的错误(这里把"值不合法"和"类型转换失败"统一成 ValueError)。
实用场景:文件操作与 with 语句
处理文件时,try-except 经常和 with 语句配合使用。with 可以自动管理文件关闭,即使在 except 中也不必手动调用 close():
import json
def load_config(filepath):
"""读取JSON配置文件,出错时返回默认配置"""
default = {"debug": False, "port": 8080}
try:
with open(filepath, "r", encoding="utf-8") as f:
config = json.load(f)
return config
except FileNotFoundError:
print(f"文件 {filepath} 不存在,使用默认配置")
return default
except json.JSONDecodeError as e:
print(f"JSON格式错误(行 {e.lineno}): {e.msg}")
return default
config = load_config("settings.json")
print(config)
这段代码分别处理了"文件不存在"和"JSON格式不合法"两种情况,每种情况都有明确的提示信息并返回一个可用的默认配置,保证程序不会因为配置文件的问题就无法启动。
自定义异常
当内置异常类型无法准确描述你的业务错误时,可以通过继承 Exception 来创建自定义异常类:
class InsufficientBalanceError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"余额不足: 当前余额 {balance},尝试支出 {amount}"
)
class Account:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientBalanceError(self.balance, amount)
self.balance -= amount
return self.balance
# 使用
account = Account(100)
try:
account.withdraw(150)
except InsufficientBalanceError as e:
print(e)
print(f"差额: {e.amount - e.balance}")
示例输出:
# 示例输出
余额不足: 当前余额 100,尝试支出 150
差额: 50
自定义异常的好处是:调用方可以通过异常类型精确区分不同的错误情况,并且通过异常对象的属性获取详细的上下文信息,而不是只能解析错误字符串。
常见错误写法与改进
1. 裸 except 捕获一切
# 不推荐
try:
do_something()
except:
pass
这种写法会吞掉所有异常,包括 KeyboardInterrupt(Ctrl+C)和 SystemExit。结果是程序出了错你完全不知道,甚至无法正常终止。正确做法是至少指定 Exception:
# 推荐
try:
do_something()
except Exception as e:
logging.error(f"操作失败: {e}")
2. try 块包含过多代码
# 不推荐:整个函数体都在 try 里
try:
data = load_data()
cleaned = clean_data(data)
result = process(cleaned)
save(result)
except Exception as e:
print(f"出错了: {e}")
# 推荐:只包裹真正可能出错的部分
data = load_data()
cleaned = clean_data(data)
try:
result = process(cleaned)
except ValueError as e:
print(f"数据处理出错: {e}")
result = fallback_process(cleaned)
save(result)
try 块越大,你越难判断异常到底是哪一行抛出的,也越容易把不该忽略的错误一并吞掉。把 try 的范围缩小到真正需要保护的代码,让错误定位更准确。
3. 用异常控制正常流程
# 不推荐
try:
value = my_dict[key]
except KeyError:
value = default_value
# 推荐:用字典自带的方法
value = my_dict.get(key, default_value)
如果有更直接的方式(如 .get()、in 检查)能避免异常,优先使用。try-except 应该处理的是"异常情况"而不是"正常分支"。不过在Python中,这条边界并不绝对——EAFP(先尝试再处理异常)本身是Python推荐的风格,关键是根据代码的可读性做判断。
异常链与 raise from
在处理一个异常时,有时你需要抛出一个不同类型的异常,同时保留原始错误信息。Python 3提供了 raise ... from ... 语法来建立异常链:
class ConfigError(Exception):
pass
def load_port(filepath):
try:
with open(filepath) as f:
return int(f.read().strip())
except (FileNotFoundError, ValueError) as e:
raise ConfigError(f"无法加载端口配置: {filepath}") from e
try:
port = load_port("port.txt")
except ConfigError as e:
print(e)
print(f"原始错误: {e.__cause__}")
示例输出(文件不存在时):
# 示例输出
无法加载端口配置: port.txt
原始错误: [Errno 2] No such file or directory: 'port.txt'
from e 把原始异常挂在新异常的 __cause__ 属性上。打印完整 Traceback 时,Python会显示两个异常及它们之间的因果关系,方便排查问题根源。
小结
try-except 是Python中处理运行时错误的基本工具。掌握它的核心在于三点:明确你要捕获什么异常、捕获后要怎么处理、以及如何确保资源被正确释放。把这三点想清楚,代码就不会出现"出了错但不知道哪里出了错"的情况。
在实际项目中,建议遵循以下做法:
- 始终指定具体的异常类型,按从具体到一般的顺序排列
except分支。 - 用
as e获取异常信息并记录日志,不要用pass默默吞掉错误。 - 优先使用
with语句管理资源,减少对finally的依赖。 - 业务逻辑复杂时,定义自定义异常类来区分不同的错误类型。
- 用
raise from保留异常链,便于定位问题的根本原因。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









