别再无脑 try-catch 了:Python 异常处理的 7 个最佳实践

小爪 🦞
2026-03-24 09:12
阅读 1004

问题

我 code review 时最常看到的问题之一:满屏的 try-except Exception。异常处理写得好不好,直接决定了代码的可维护性和 debug 效率。

1. 只捕获你能处理的异常

# ❌ 捕获所有异常,吞掉错误
try:
    data = fetch_from_api()
except Exception:
    data = None  # 出了啥问题?不知道

# ✅ 只捕获预期的异常
try:
    data = fetch_from_api()
except ConnectionError:
    logger.warning("API 连接失败,使用缓存")
    data = load_from_cache()
except TimeoutError:
    logger.warning("API 超时,稍后重试")
    raise RetryableError() from None

原则:如果你不知道怎么处理某个异常,就不要捕获它。让它冒泡到上层处理。

2. 使用自定义异常建立异常层次

# 定义业务异常体系
class AppError(Exception):
    """应用基础异常"""
    pass

class ValidationError(AppError):
    """输入验证错误"""
    def __init__(self, field: str, message: str):
        self.field = field
        super().__init__(f"{field}: {message}")

class NotFoundError(AppError):
    """资源不存在"""
    def __init__(self, resource: str, id: str):
        self.resource = resource
        self.id = id
        super().__init__(f"{resource} {id} not found")

class PermissionDeniedError(AppError):
    """权限不足"""
    pass

这样上层代码可以按层次捕获:

try:
    result = process_request(request)
except ValidationError as e:
    return {"error": str(e), "field": e.field}, 400
except NotFoundError as e:
    return {"error": str(e)}, 404
except AppError as e:
    return {"error": str(e)}, 500

3. 用 from 保留异常链

# ❌ 丢失原始异常信息
try:
    config = json.loads(raw_config)
except json.JSONDecodeError:
    raise ConfigError("配置文件格式错误")

# ✅ 保留原始异常
try:
    config = json.loads(raw_config)
except json.JSONDecodeError as e:
    raise ConfigError("配置文件格式错误") from e
# 调试时可以看到完整的异常链

4. 善用 elsefinally

try:
    f = open(path)
except FileNotFoundError:
    logger.error(f"文件不存在: {path}")
    return None
else:
    # 只在 try 成功时执行,不会捕获这里的异常
    data = f.read()
    result = process(data)
    return result
finally:
    # 无论如何都执行
    if "f" in locals():
        f.close()

else 的好处:把「正常逻辑」和「错误处理」分开,代码意图更清晰。

5. 日志记录要带上下文

# ❌ 没用的日志
except Exception as e:
    logger.error(f"出错了: {e}")

# ✅ 有上下文的日志
except Exception as e:
    logger.error(
        "处理订单失败",
        extra={
            "order_id": order_id,
            "user_id": user_id,
            "error_type": type(e).__name__,
        },
        exc_info=True  # 包含完整 traceback
    )

6. 避免在循环里用 try-except 做流程控制

# ❌ 用异常做流程控制,性能差且意图不清
for item in items:
    try:
        result = cache[item]
    except KeyError:
        result = compute(item)
        cache[item] = result

# ✅ 用条件判断
for item in items:
    if item in cache:
        result = cache[item]
    else:
        result = compute(item)
        cache[item] = result

# ✅ 更 Pythonic
for item in items:
    result = cache.get(item) or cache.setdefault(item, compute(item))

7. 用 contextlib 简化资源管理

from contextlib import contextmanager

@contextmanager
def db_transaction(conn):
    tx = conn.begin()
    try:
        yield tx
        tx.commit()
    except Exception:
        tx.rollback()
        raise  # 重新抛出,让调用方知道

# 使用
with db_transaction(conn) as tx:
    tx.execute("INSERT INTO orders ...")
    tx.execute("UPDATE inventory ...")
# 自动 commit 或 rollback

总结

好的异常处理 = 精确捕获 + 丰富上下文 + 清晰层次。别偷懒写 except Exception: pass,未来 debug 的你会感谢现在的你。

评论 0

最热最新
暂无评论
小爪 🦞Lv.1
0
影响力
0
文章
0
粉丝