Python 异常处理与资源管理

Python 用异常处理错误,用上下文管理器管理资源。两者组合构成 Python 的”安全网”——既保证错误不被静默吞掉,也保证资源不会泄漏。


异常的概念

Python 中,异常是程序运行时发生的错误。当错误发生时,Python 创建一个异常对象;若未被处理,程序终止并显示 traceback。

try/except 捕获异常 → 程序继续运行,而非崩溃

异常层次结构

BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception                  ← 应用程序应捕获的基类
    ├── StopIteration
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   ├── OverflowError
    │   └── FloatingPointError
    ├── AssertionError
    ├── AttributeError
    ├── EOFError
    ├── ImportError
    │   └── ModuleNotFoundError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── NameError
    │   └── UnboundLocalError
    ├── OSError
    │   └── IOError
    ├── RuntimeError
    │   ├── NotImplementedError
    │   └── RecursionError
    ├── SyntaxError
    ├── SystemError
    ├── TypeError
    ├── ValueError
    └── UnicodeError

核心原则:只捕获 Exception 及其子类,不要捕获 BaseException(会捕获 SystemExitKeyboardInterrupt,阻止程序正常退出)。


基本异常处理

四个关键字:

关键字时机
try包裹可能出错的代码
except捕获指定类型的异常
elsetry 无异常时执行
finally无论如何都执行
try:
    result = 10 / 0
except ZeroDivisionError:
    print("不能除以零!")
else:
    print("操作成功")
finally:
    print("始终执行")

捕获多个异常

try:
    value = int(user_input)
    result = 100 / value
except ValueError:
    print("请输入有效数字")
except ZeroDivisionError:
    print("不能为零")
except (TypeError, KeyError) as e:
    print(f"意外错误:{e}")

捕获所有异常(危险)

try:
    do_work()
except:
    # ❌ 裸 except 捕获一切,包括 SystemExit
    pass

应至少捕获 Exception

except Exception as e:
    logging.exception("捕获到异常")

raise 与异常链

主动抛出

raise ValueError("无效参数")

异常链(Python 3.0+)

在处理异常时保留原始上下文:

try:
    int('abc')
except ValueError as e:
    raise RuntimeError('转换失败') from e

ExceptionGroup (Python 3.11+)

同时处理多个并发异常:

def f():
    excs = []
    for i in range(3):
        try:
            risky_op(i)
        except Exception as e:
            excs.append(e)
    if excs:
        raise ExceptionGroup("多个任务失败", excs)
 
try:
    f()
except* ValueError as eg:
    print(f"值错误:{eg.exceptions}")
except* TypeError as eg:
    print(f"类型错误:{eg.exceptions}")

自定义异常

class CustomError(Exception):
    def __init__(self, message, code=None):
        self.message = message
        self.code = code
        super().__init__(message)
 
try:
    raise CustomError("资源未找到", code=404)
except CustomError as e:
    print(f"错误 {e.code}: {e.message}")

常用内置异常速查

异常触发场景
ValueError参数类型正确但值不合法
TypeError操作应用于错误类型
KeyError字典键不存在
IndexError序列索引越界
AttributeError对象没有该属性
ImportError导入失败
OSError操作系统错误(含文件不存在)
RuntimeError通用运行时错误
NotImplementedError抽象方法未实现
RecursionError超过最大递归深度

warnings 模块

警告不会中断程序,适合标记废弃功能:

import warnings
 
def old_function():
    warnings.warn("此函数将在 v2.0 移除", DeprecationWarning)
 
# 将警告转为异常(调试用):
warnings.simplefilter('error')

异常处理最佳实践

  1. 精确捕获:只捕获能处理的异常类型,别用裸 except:
  2. 在合适的层级处理:底层函数抛出,上层决定如何响应
  3. 不吞异常:捕获后至少 log,不要静默 pass
  4. 不用异常做控制流:异常是”异常”情况,不是条件分支
  5. finally 中不 return/break:会吞掉异常(3.14+ 有 SyntaxWarning)

上下文管理器与资源管理

with 语句是 Python 管理资源的核心机制——保证无论是否发生异常,资源都会被正确释放

基本用法

with open("file.txt", "r") as f:
    for line in f:
        print(line)
# 文件已自动关闭——无需 f.close()

同时管理多个资源

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

实现上下文管理器

方式一:定义 __enter____exit__

class ManagedFile:
    def __init__(self, filename):
        self.filename = filename
 
    def __enter__(self):
        self.file = open(self.filename, 'r')
        return self.file
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        # 返回 True 可抑制异常(通常不推荐)

方式二:使用 contextlib(推荐):

from contextlib import contextmanager
 
@contextmanager
def managed_file(filename):
    f = open(filename, 'r')
    try:
        yield f
    finally:
        f.close()
 
with managed_file("data.txt") as f:
    print(f.read())

contextlib 常用工具

from contextlib import closing, suppress, redirect_stdout
 
# closing — 把有 close() 方法的对象变成上下文管理器
from urllib.request import urlopen
with closing(urlopen('https://example.com')) as page:
    data = page.read()
 
# suppress — 忽略指定异常
with suppress(FileNotFoundError):
    os.remove('temp.txt')  # 文件不存在也不报错
 
# ExitStack — 动态管理多个上下文
from contextlib import ExitStack
with ExitStack() as stack:
    files = [stack.enter_context(open(f)) for f in filenames]
    # 所有文件在退出时自动关闭

上下文管理器 vs try/finally

# try/finally — 手动管理
f = open("file.txt")
try:
    data = f.read()
finally:
    f.close()
 
# with — 上下文管理器自动管理
with open("file.txt") as f:
    data = f.read()

永远优先使用 with——更短、更安全、不会忘记清理。


关联