ChatGPT Plus 辅助分析 Python 异常堆栈:从日志脱敏到回归测试
在 Python 项目中遇到异常时,直接把整段日志复制给 ChatGPT Plus,再输入"帮我修复",通常不是最可靠的做法。
完整日志可能包含无关信息、真实路径、服务器地址和用户数据;缺少运行环境、输入样本和业务约束时,模型也只能根据常见情况猜测。一个更稳妥的调试流程应该包括:
- 从堆栈底部确认异常类型和触发位置;
- 对日志和配置进行脱敏;
- 提取最小可复现代码;
- 第一轮只分析原因,不修改代码;
- 第二轮限定最小修复范围;
- 使用本地测试验证,而不是直接相信生成结果。
本文用一个 Decimal 与 float 混用的示例,演示如何让 ChatGPT Plus 辅助分析 Python 报错,同时保留人工审查、版本控制和回归测试(环球巴士整理)。
一、示例问题:Decimal 与 float 混用
假设项目中有一个用于计算含税价格的函数:
python
from decimal import Decimal
def price_with_tax(price: Decimal, tax_rate: float) -> Decimal:
return price * (Decimal("1") + tax_rate)
print(price_with_tax(Decimal("100.00"), 0.06))
运行代码:
bash
python pricing.py
会得到类似下面的异常:
text
Traceback (most recent call last):
File "pricing.py", line 8, in <module>
print(price_with_tax(Decimal("100.00"), 0.06))
File "pricing.py", line 5, in price_with_tax
return price * (Decimal("1") + tax_rate)
~~~~~~~~~~~~~^~~~~~~~~~
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
问题并不复杂:Decimal("1") 是 Decimal,而 tax_rate 是 float,Python 不允许直接把这两种数值类型相加。
但真实项目中的堆栈往往更长,还可能经过 Web 框架、数据库驱动和多个业务函数。如果只把日志交给模型,而不说明哪些代码属于自己、哪些来自依赖,回答很容易偏离问题。
二、先学会从异常堆栈底部向上读
Python 异常堆栈通常可以按下面的顺序阅读。
1. 最后一行:异常类型和直接原因
text
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
这一行给出了三个关键信息:
- 异常类型是
TypeError; - 失败运算是加法;
- 两边的类型分别为
Decimal和float。
2. 倒数第二个业务栈帧:真正触发错误的位置
text
File "pricing.py", line 5, in price_with_tax
如果堆栈中同时存在第三方库和项目代码,优先检查最靠近异常底部的业务代码栈帧,而不是立即修改依赖包。
3. 更上层栈帧:输入从哪里进入
text
File "pricing.py", line 8, in <module>
这一层说明函数接收到的 tax_rate 是浮点数 0.06。如果真实项目中的值来自 JSON、数据库或环境变量,还要继续检查类型转换发生在哪里。
三、把日志交给 ChatGPT 前先脱敏
开发日志中常见的敏感信息包括:
- Windows 或 Linux 用户目录;
- 服务器 IP、端口和内部域名;
- 数据库连接字符串;
- Cookie、访问令牌和 API Key;
- 邮箱、手机号、订单号和用户 ID;
- 未公开的仓库地址和项目名称。
例如下面的日志不适合直接复制:
text
POST internal-api.example.local/orders/849312
Authorization: Bearer TOKEN_VALUE
Database: postgresql://admin:PASSWORD_VALUE@10.0.0.8/prod
User email: user@example.com
可以改成:
text
POST INTERNAL_HOST/orders/ORDER_ID
Authorization: Bearer YOUR_TOKEN
Database: postgresql://USER:PASSWORD@HOST/DATABASE
User email: USER_EMAIL
脱敏不能只做字符串替换,还要检查上下文。某些订单号、文件名或目录结构即使没有姓名,也可能暴露业务信息。
四、第一轮提示词:只分析,不生成修复代码
把最小代码和异常信息整理好后,可以先让 ChatGPT Plus 解释问题:
text
请分析下面的 Python 异常,只说明能够从代码和堆栈直接证明的原因,暂时不要修改代码。
运行环境:
- Python 3.12
- 不使用第三方数值库
业务约束:
- price 使用 Decimal;
- tax_rate 表示 0 到 1 之间的税率;
- 金额计算不能退回 float;
- 当前公共函数名称和参数数量不能改变。
请按下面格式输出:
1. 直接原因;
2. 数据类型如何进入函数;
3. 可能受影响的边界情况;
4. 修复前需要确认的问题。
代码:
[粘贴最小复现代码]
异常:
[粘贴脱敏后的堆栈]
这一步故意不要求模型立即给出代码,原因有两个:
- 先确认模型是否正确理解异常;
- 避免它在没有业务约束时进行大范围重构。
如果第一轮分析已经出现错误,例如建议把全部金额改成 float,就应该先纠正前提,而不是继续让它生成补丁。
五、第二轮提示词:限定最小修复范围
确认原因后,再提出具体修改要求:
text
请给出最小修复方案,必须满足:
- price 和返回值继续使用 Decimal;
- tax_rate 允许传入 Decimal、字符串或整数;
- 不接受 float,避免二进制浮点误差被带入金额计算;
- tax_rate 必须位于 0 到 1 之间,包含边界;
- 非法类型抛出 TypeError;
- 非法范围抛出 ValueError;
- 不引入第三方依赖;
- 输出修改后的函数和 pytest 测试;
- 不修改其他文件。
这里主动规定"不接受 float",是为了让接口边界更明确。如果业务系统当前已经大量传入浮点数,也可以选择兼容,但必须先决定如何转换以及是否接受精度变化。
六、一个可审查的最小修复
下面是一种实现方式:
python
from decimal import Decimal, InvalidOperation
from typing import Union
TaxRateInput = Union[Decimal, str, int]
def normalize_tax_rate(value: TaxRateInput) -> Decimal:
if isinstance(value, float):
raise TypeError("tax_rate must not be float")
if not isinstance(value, (Decimal, str, int)):
raise TypeError("tax_rate must be Decimal, str, or int")
try:
rate = value if isinstance(value, Decimal) else Decimal(str(value))
except InvalidOperation as exc:
raise ValueError("tax_rate is not a valid decimal value") from exc
if not rate.is_finite():
raise ValueError("tax_rate must be finite")
if not Decimal("0") <= rate <= Decimal("1"):
raise ValueError("tax_rate must be between 0 and 1")
return rate
def price_with_tax(price: Decimal, tax_rate: TaxRateInput) -> Decimal:
if not isinstance(price, Decimal):
raise TypeError("price must be Decimal")
rate = normalize_tax_rate(tax_rate)
return price * (Decimal("1") + rate)
这个版本做了几件事:
- 明确拒绝
float; - 将字符串和整数统一转换为
Decimal; - 区分类型错误与数值范围错误;
- 保留原函数名称和参数数量;
- 没有引入第三方依赖。
是否需要单独的 normalize_tax_rate 函数,取决于项目规模。如果转换逻辑只使用一次,可以放回原函数;如果多个模块都处理税率,独立函数更便于测试和复用。
七、补充 pytest 回归测试
为修复代码添加测试:
python
from decimal import Decimal
import pytest
from pricing import price_with_tax
@pytest.mark.parametrize(
("price", "tax_rate", "expected"),
[
(Decimal("100.00"), Decimal("0"), Decimal("100.00")),
(Decimal("100.00"), Decimal("0.06"), Decimal("106.0000")),
(Decimal("100.00"), "0.06", Decimal("106.0000")),
(Decimal("100.00"), 1, Decimal("200.00")),
(Decimal("0"), "0.25", Decimal("0.00")),
],
)
def test_price_with_tax_valid_input(price, tax_rate, expected):
assert price_with_tax(price, tax_rate) == expected
@pytest.mark.parametrize("tax_rate", [0.06, object()])
def test_price_with_tax_rejects_invalid_type(tax_rate):
with pytest.raises(TypeError):
price_with_tax(Decimal("100.00"), tax_rate)
@pytest.mark.parametrize(
"tax_rate",
[Decimal("-0.01"), Decimal("1.01"), Decimal("NaN"), "invalid"],
)
def test_price_with_tax_rejects_invalid_value(tax_rate):
with pytest.raises(ValueError):
price_with_tax(Decimal("100.00"), tax_rate)
def test_price_with_tax_rejects_non_decimal_price():
with pytest.raises(TypeError):
price_with_tax(100.0, Decimal("0.06"))
运行测试:
bash
python -m pytest -q
如果项目还使用 Ruff 或 Mypy,可以继续执行:
bash
ruff check .
mypy .
工具名称和配置要以项目实际情况为准,不需要为了文章示例临时引入一套新的检查工具。
八、测试代码也要人工审查
AI 生成测试不代表测试一定有效。至少要检查下面几个问题:
1. 测试是否覆盖了业务边界
本例需要覆盖:
- 税率等于
0; - 税率等于
1; - 税率小于
0; - 税率大于
1; - 浮点数输入;
- 无法转换的字符串;
price不是Decimal。
2. 测试是否只验证了当前实现
如果测试完全照着实现细节编写,可能只是重复代码,而不是验证业务规则。测试应描述外部可观察行为,例如返回值和异常类型。
3. 是否为了通过测试而降低要求
当测试失败时,不应该直接删除断言、扩大允许范围或捕获所有异常。先确认失败来自需求、实现、测试数据还是运行环境。
九、使用 git 检查修改范围
在接受任何 AI 生成的修改前,先查看工作区:
bash
git status --short
git diff --check
git diff
重点检查:
- 是否修改了约定范围以外的文件;
- 是否意外改变公共函数签名;
- 是否加入没有说明的新依赖;
- 是否把真实日志、令牌或本地路径写进测试;
- 是否删除了原有测试;
- 是否出现大面积格式化导致 Diff 难以审查。
对于规模较大的修改,可以要求 ChatGPT 先输出文件清单和实施计划,确认后再逐个文件处理。
十、适合复制的异常分析模板
下面这个模板可以用于大多数 Python 报错:
text
请分析这个 Python 异常。
运行环境:
- Python 版本:
- 操作系统:
- 主要依赖及版本:
预期行为:
实际行为:
最小复现步骤:
1.
2.
3.
业务约束:
- 不能修改:
- 必须保留:
- 允许修改:
请先只输出:
1. 能够从代码直接证明的原因;
2. 触发条件;
3. 仍缺少的信息;
4. 最小修复计划。
在我确认计划前,不要生成完整重构代码。
模板不需要每次全部填写。简单问题可以缩短,但运行环境、预期行为、实际行为和最小复现通常不能省略。
十一、常见问题
1. 可以把整个项目压缩后直接上传吗?
不建议把未筛选的完整项目直接作为第一步。应先排除 .env、密钥、证书、数据库备份、用户数据、构建产物和无关依赖,再从最小模块开始分析。
2. 堆栈太长,应该截取哪一部分?
至少保留异常类型、完整错误消息、最靠近异常位置的业务栈帧,以及能够说明输入来源的上层栈帧。不要只截最后一句,也不必保留几千行重复日志。
3. ChatGPT Plus 给出的代码可以直接用于生产环境吗?
不可以直接使用。生成代码应经过人工审查、本地测试、集成测试和必要的安全检查,高风险变更还需要审批和回滚方案。
4. 为什么同一个报错每次得到的建议不同?
模型输出存在一定变化,上下文和提示词也会影响结果。使用固定的最小复现、明确约束和可执行测试,比依赖某一次回答更可靠。
十二、总结
使用 ChatGPT Plus 分析 Python 异常堆栈时,重点不是让模型尽快输出一段修复代码,而是建立一个可复现、可审查、可验证的流程:
- 从堆栈底部确认异常和触发位置;
- 对日志、路径和配置进行脱敏;
- 提取最小复现代码;
- 第一轮只分析原因;
- 第二轮限制修改范围;
- 添加边界测试和异常测试;
- 通过
git diff与本地测试验证结果; - 最终由开发者决定是否采用修改。
ChatGPT Plus 可以帮助缩短信息整理和方案探索的时间,但代码是否正确,仍然要由运行结果、测试和人工评审来证明(本文由环球巴士整理,一站式账号服务)。
资料说明
- Python 官方文档:
decimal模块 - Python 官方文档:异常与回溯信息
- pytest 官方文档:参数化测试与异常断言
本文示例为原创最小复现,代码仅用于演示异常分析方法。实际项目应根据业务规则、依赖版本和团队规范调整。