page contents

看不懂 Python 报错?80% 的报错三行就够

不少人第一反应是从第一行开始翻,看到一半又是文件路径,又是模块调用,脑子已经乱了。其实 Python 的报错没那么客气,它通常把最有用的信息放在最后。

attachments-2026-08-20HpUCNF6a83b6ae72d94.png脚本刚跑两秒,屏幕上刷出十几行红字。

不少人第一反应是从第一行开始翻,看到一半又是文件路径,又是模块调用,脑子已经乱了。其实 Python 的报错没那么客气,它通常把最有用的信息放在最后。

先看这段:

Traceback (most recent call last):
  File "/srv/jobs/import_order.py", line 52, in build_order
    pay_cent = int(row["pay_amount"])
ValueError: invalid literal for int() with base 10: '¥89.00'

这种报错,我一般只盯最后三行。

File "/srv/jobs/import_order.py", line 52, in build_order
pay_cent = int(row["pay_amount"])
ValueError: invalid literal for int() with base 10: '¥89.00'

第一行告诉你:哪里炸了。

第二行告诉你:哪句代码炸了。

第三行告诉你:为什么炸。

这里已经很清楚了。代码想把字符串转成整数,但拿到的不是 "89",而是 "¥89.00"。int() 不认识人民币符号,也不接受带小数点的金额字符串。

这时候没必要研究 int() 的底层实现,更不用怀疑 Python 环境。输入数据脏了,先处理数据。

我会直接把金额转换收口,不让业务代码到处裸调 int():

from decimal import Decimal, InvalidOperation


def amount_to_cent(raw_value: object) -> int:
    text = str(raw_value).strip()
    text = text.replace("¥", "").replace(",", "")

    if not text:
        raise ValueError("支付金额为空")

    try:
        return int(Decimal(text) * 100)
    except InvalidOperation as exc:
        raise ValueError(f"无法识别支付金额: {raw_value!r}") from exc

调用时也别把错误吞掉:

def build_order(row: dict) -> dict:
    return {
        "order_no": row["order_no"],
        "pay_cent": amount_to_cent(row["pay_amount"]),
    }

这样下次再碰到 "待确认"、"--" 之类的值,日志会直接告诉你是哪条数据有问题,而不是只丢一个模糊的类型转换异常。

再看一个很常见的报错:

  File "/srv/jobs/check_user.py", line 28, in load_profile
    city = profile["address"]["city"]
TypeError: 'NoneType' object is not subscriptable

最后一行里的 NoneType 很关键。

profile["address"]["city"] 这段代码连续取了两层字段,其中某一层是 None,代码却还在继续使用 []。

很多人看到这里会改成:

city = profile.get("address", {}).get("city", "")

这段代码看起来不报错了,但我不太喜欢这么处理。因为 address 字段缺失、值为 None、城市为空,最后全被压成了空字符串。脚本是跑完了,数据也被悄悄写坏了。

我一般把判断拆开:

def read_city(profile: dict) -> str:
    address = profile.get("address")

    if address is None:
        raise ValueError("用户地址未返回")

    if not isinstance(address, dict):
        raise TypeError(f"address 类型异常: {type(address).__name__}")

    city = address.get("city")
    if not city:
        raise ValueError("用户城市为空")

    return city

报错不是越少越好。该报的时候尽早报,而且要报得能查。

还有一种报错,最后三行只能看出落点,看不出是谁把错误数据传进来的:

  File "/srv/service/stock.py", line 41, in reserve
    remain = stock - request_count
TypeError: unsupported operand type(s) for -: 'int' and 'str'

stock 是整数,request_count 是字符串。问题已经定位了一半,接下来往上看一层调用栈,找谁调用了 reserve()。

  File "/srv/api/create_order.py", line 76, in submit
    reserve(item_stock, payload["count"])

看到这里就够了。接口收到的 count 没做转换,字符串一路传到了库存计算。

修正位置应该放在参数入口,而不是在库存函数里见什么转什么:

def parse_buy_count(payload: dict) -> int:
    raw_count = payload.get("count")

    try:
        count = int(raw_count)
    except (TypeError, ValueError) as exc:
        raise ValueError(f"购买数量不合法: {raw_count!r}") from exc

    if count <= 0:
        raise ValueError(f"购买数量必须大于 0,当前值: {count}")

    return count

这也是我看 Python 报错时的顺序:

先看最后一行的异常类型和说明,再看上一行具体代码,最后看文件名和行号。还不够,再往上找一层调用者。

不要一上来就复制整段 Traceback 去搜索。网上搜出来的通常只是同一种异常,未必是同一个问题。KeyError 可能是字段没返回,也可能是字段名拼错;AttributeError 可能是对象为 None,也可能是拿错了类型。

真正有用的线索,一直都在你自己的那几行报错里。

Python 已经把出事地点圈出来了。先把最后三行看明白,大部分问题根本走不到“疑难杂症”那一步。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

 

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2323 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. Pack 2323 文章
  3. 小柒 2228 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章