Skip to content

Python 异常处理

异常是运行时的错误信号。用 try / except 可以把正常流程出错时的处理分开,避免程序直接崩溃;elsefinallyraise 则用于更精细的控制。与 returnfinally 的先后关系return 语句

python
try:
    result = 10 / 0
except ZeroDivisionError:
    print("不能除以零")

try / except 如何工作

  1. 先执行 try 里的语句。
  2. 发生异常,跳过所有 except
  3. 若发生异常,停止 try 中剩余语句,按顺序匹配 except 的类型;第一个匹配的子句执行后,其余 except 忽略。
python
try:
    user_input = input("请输入一个整数:")
    number = int(user_input)
    print(f"你输入了:{number}")
except ValueError:
    print("这不是合法整数")

用户输入非数字时,int() 抛出 ValueError,由对应 except 处理。

多种异常

多个 except 分别处理不同类型;顺序很重要:应把更具体的异常放在更通用的之前(例如 FileNotFoundErrorOSError 之前)。

python
try:
    with open("data.txt", "r", encoding="utf-8") as f:
        content = f.read()
    value = int(content)
except FileNotFoundError:
    print("文件不存在")
except ValueError:
    print("文件内容不是合法整数")
except PermissionError:
    print("没有读权限")

同一分支捕获多种异常:

python
def fetch_api_data():
    raise ConnectionError("断网")


def process_data(data):
    pass


try:
    data = fetch_api_data()
    process_data(data)
except (ConnectionError, TimeoutError):
    print("网络请求失败")

绑定异常对象:as

python
try:
    result = 1 + 2 + "three"
except TypeError as error:
    print(type(error).__name__)
    print(error)
    print(error.args)

error 即异常实例,便于日志与调试。

else 子句

elsetry 未抛出任何异常时执行,适合把「成功后的逻辑」与 except 分开,避免把可能引发其它类型异常的代码全塞进 try,误被外层 except 接住。

python
import json


def default_config():
    return {}


def validate_config(data):
    pass  # 按项目规则校验


def load_config(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
    except FileNotFoundError:
        print("配置文件缺失,使用默认配置")
        return default_config()
    else:
        validate_config(data)
        return data
    finally:
        print("配置加载流程结束")

子句顺序必须是:tryexceptelsefinallyelse / finally 可选)。

finally:清理

finally 是否发生异常都会执行,常用来关闭连接、文件等。若在 tryreturn,仍会先执行 finally 再返回;finally 里若再 return覆盖前面的返回值(尽量少用)。

python
import sqlite3

conn = None
try:
    conn = sqlite3.connect(":memory:")
    conn.execute("CREATE TABLE t (x INTEGER)")
except sqlite3.Error as e:
    print("数据库错误:", e)
    raise
finally:
    if conn is not None:
        conn.close()

raise:主动抛出

python
def withdraw(account, amount):
    if amount <= 0:
        raise ValueError("取款金额必须为正")
    if amount > account.balance:
        raise ValueError("余额不足")
    account.balance -= amount
    return account.balance

except 里写 raise(不带表达式)表示原样继续抛出当前异常,便于先打日志再交给上层:

python
import logging

logger = logging.getLogger(__name__)


def risky():
    raise OSError("磁盘只读")


try:
    risky()
except OSError as e:
    logger.error("IO 失败: %s", e)
    raise

异常链:from / from None

python
class CacheError(Exception):
    pass


class DataLoadError(Exception):
    pass


def load_from_cache():
    raise CacheError("缓存未命中")


try:
    load_from_cache()
except CacheError as e:
    raise DataLoadError("加载失败") from e

from e 保留原因链,回溯更清晰。

python
import json

try:
    json.loads("{")  # 非法 JSON
except json.JSONDecodeError:
    raise ValueError("数据格式无效") from None

from None隐藏原始异常,只显示新异常;会丢掉部分调试信息,谨慎使用

以下 requests 示例需 pip install requests;若不想依赖第三方库,可改用 urllib 等标准库并捕获其异常。

python
import requests
from requests.exceptions import RequestException, Timeout, HTTPError

def fetch_user(user_id, max_retries=3):
    url = f"https://api.example.com/users/{user_id}"
    for attempt in range(max_retries):
        try:
            r = requests.get(url, timeout=10)
            r.raise_for_status()
            return r.json()
        except Timeout:
            print(f"超时(第 {attempt + 1}/{max_retries} 次)")
            if attempt == max_retries - 1:
                raise
        except HTTPError as e:
            if e.response.status_code == 404:
                return None
            raise
        except RequestException as e:
            print(f"请求失败: {e}")
            raise

自定义异常

python
class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"无法取出 {amount},当前余额 {balance}")


class AccountLockedError(Exception):
    pass

继承 Exception(一般不要直接继承 BaseException,以免干扰 KeyboardInterruptSystemExit 等)。

实践建议

  • try 里尽量只包预期可能失败」的几行,不要把整个函数塞进去。
  • 避免裸 except::会连 KeyboardInterruptSystemExit 也吞掉。应写 except Exception记录后 raise,或捕获具体类型
python
import logging

logger = logging.getLogger(__name__)


def risky_operation():
    raise RuntimeError("演示")


# 不推荐
try:
    risky_operation()
except:
    print("出错了")

# 更好
try:
    risky_operation()
except Exception:
    logger.exception("未预期错误")
    raise
  • BaseExceptionException:用户自定义与日常捕获的多数是 Exception 子类;KeyboardInterrupt 继承 BaseException,不会被 except Exception 捕获(通常也不应去捕获它,除非有特殊需求)。

调试与回溯

回溯(traceback)从最后一行看「直接原因」,向上看调用链。在返回给前端的错误信息里是否附带内部细节,要兼顾安全可排查性

python
def calculate_total(items):
    return sum(item["price"] * item["quantity"] for item in items)


def process_cart(cart_data):
    try:
        total = calculate_total(cart_data["items"])
        return {"total": total, "status": "success"}
    except KeyError as e:
        return {
            "total": 0,
            "status": "error",
            "message": f"缺少字段: {e}",
        }
    except TypeError as e:
        return {
            "total": 0,
            "status": "error",
            "message": f"类型无效: {e}",
        }

小结

语法作用
try / except捕获并处理异常
as绑定异常实例
else**try 无异常时执行
finally无论是否异常都执行(清理)
raise抛出异常;裸 raise 继续传播
from / from None异常链或抑制链

参考