Python3 错误和异常

在 Python 3 中,错误(Errors)异常(Exceptions)是程序运行时出现的问题。Python 拥有一套非常优雅且强大的异常处理机制,遵循 “EAFP” 原则(Easier to Ask for Forgiveness than Permission,即“先做再道歉”,也就是先尝试执行代码,如果出错了再捕获处理),这与 C/Java 等语言的 “LBYL”(Look Before You Leap,先检查后执行)风格截然不同。

下面我将从基础语法核心流程自定义异常最佳实践,为你系统梳理 Python 3 的异常处理。


一、 基础语法:try...except

这是异常处理的基石。将可能出错的代码放在 try 块中,将处理错误的逻辑放在 except 块中。

纯文本
try:
    # 尝试执行的代码
    result = 10 / 0
except ZeroDivisionError:
    # 只有当发生 ZeroDivisionError 时才会执行这里
    print("❌ 错误:除数不能为零!")
except TypeError as e:
    # 可以捕获多种不同类型的异常
    print(f"❌ 类型错误: {e}")
else:
    # 【可选】如果没有发生任何异常,则执行这里
    print(f"✅ 计算结果: {result}")
finally:
    # 【可选】无论是否发生异常,最后都会执行这里(常用于清理资源)
    print("🔚 程序执行完毕。")

输出:

纯文本
❌ 错误:除数不能为零!
🔚 程序执行完毕。

二、 异常处理的核心组件

1. 捕获特定异常 vs 通用异常

  • 推荐:始终尽量捕获具体的异常类型(如 FileNotFoundError, KeyError)。
  • 谨慎:使用裸 except:except Exception: 会捕获所有异常,这可能会掩盖真正的 Bug(如拼写错误导致的 NameError)。
纯文本
# ❌ 糟糕的做法:掩盖了所有潜在问题
try:
    do_something()
except:
    pass

# ✅ 好的做法:只捕获预期的异常
try:
    value = my_dict["key"]
except KeyError:
    value = "default_value"

2. as 关键字:获取异常详情

使用 as e 可以将异常对象赋值给变量 e,从而打印详细的错误信息或日志。

纯文本
try:
    int("not_a_number")
except ValueError as e:
    print(f"捕获到值错误: {e}") 
    # 输出: 捕获到值错误: invalid literal for int() with base 10: 'not_a_number'

3. else 子句:区分正常逻辑与异常逻辑

很多新手喜欢把正常逻辑也写在 try 里,但这会导致难以定位到底是哪一行出了错。else 块确保了只有在 try 完全成功时才执行后续逻辑。

4. finally 子句:确保资源释放

无论程序是正常结束还是崩溃,finally 都会执行。它是关闭文件、数据库连接或网络套接字的最佳位置。

纯文本
file = None
try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("文件不存在")
finally:
    if file:
        file.close()  # 确保文件被关闭

(注:现代 Python 更推荐使用 with 语句自动管理资源,见下文最佳实践)


三、 主动抛出异常:raise

当你发现程序处于非法状态时,可以主动抛出异常,中断程序并通知调用者。

纯文本
def set_age(age):
    if not isinstance(age, int):
        raise TypeError("年龄必须是整数")
    if age < 0 or age > 150:
        raise ValueError("年龄必须在 0 到 150 之间")

    print(f"年龄设置为: {age}")

try:
    set_age(-5)
except ValueError as e:
    print(f"输入无效: {e}")

四、 自定义异常类

在大型项目中,为了更清晰地表达业务逻辑错误,建议继承内置的 Exception 类创建自定义异常。

纯文本
class InsufficientFundsError(Exception):
    """余额不足异常"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"余额不足! 当前余额: {balance}, 尝试取款: {amount}")

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

try:
    withdraw(100, 200)
except InsufficientFundsError as e:
    print(e) 
    # 输出: 余额不足! 当前余额: 100, 尝试取款: 200

五、 常见内置异常速查表

异常名称触发场景
SyntaxError代码语法错误(解释器阶段,无法被 try-except 捕获)
NameError使用了未定义的变量
TypeError对类型不支持的操作(如 "1" + 1
ValueError传入正确的类型但无效的值(如 int("abc")
IndexError列表索引越界
KeyError字典键不存在
FileNotFoundError文件路径不存在
AttributeError对象没有该属性或方法
ImportError / ModuleNotFoundError模块导入失败

六、 最佳实践与避坑指南

1. 优先使用 with 语句 (Context Manager)

对于文件、网络连接等资源,with 语句会自动处理 __enter____exit__,即使发生异常也能确保资源正确关闭,比 try-finally 更简洁。

纯文本
# ✅ 推荐
with open("data.txt", "r") as f:
    content = f.read()
# 文件在这里自动关闭

2. 不要滥用异常控制流程

异常处理的性能开销比普通 if-else 大得多。如果可以通过简单的条件判断避免错误,请优先使用 if

纯文本
# ❌ 慢:依赖异常
try:
    value = my_dict["key"]
except KeyError:
    value = "default"

# ✅ 快:直接检查
value = my_dict.get("key", "default")

3. 异常链 (Exception Chaining)

在捕获一个异常并抛出新异常时,使用 raise ... from ... 可以保留原始异常的追踪信息,方便调试。

纯文本
try:
    db.connect()
except DatabaseConnectionError as e:
    raise ApplicationStartupError("无法启动应用,数据库连接失败") from e

4. 记录日志而不是静默忽略

永远不要写空的 except 块。至少应该记录日志,否则你的程序会在无声无息中失败,导致后期排查极其困难。

纯文本
import logging

logger = logging.getLogger(__name__)

try:
    process_data()
except Exception as e:
    logger.error(f"数据处理失败: {e}", exc_info=True) # exc_info=True 会打印堆栈轨迹

总结

Python 的异常处理旨在让代码健壮且易读

  • 小范围捕获:只捕获你预期并能处理的异常。
  • 快速失败:遇到非法状态尽早 raise
  • 资源安全:多用 with,少用手动 close
  • 保留现场:使用 from e 和日志记录,方便排查问题。

如果你在开发中遇到了特定的报错(Traceback),可以把错误信息发给我,我帮你分析原因并提供修复方案!