一个人同时维护十几个数据分析、抓取、自动化脚本的时候,真正让人头大的往往不是那些复杂的算法问题,而是一批看起来"很低级"的小错误——它们出现的频率高得惊人,而且类型高度固定。
现象
一批脚本要么反复抛异常,要么算出来一组"不可能的数值":占比超过 100%、平均值是天文数字、某天的统计直接为空。报错信息五花八门,但翻来覆去就那么几类。典型的有下面五种:
第一类,类型不匹配。把整个列表传给 %d:
items = [1, 2, 3]
print("共 %d 条" % items)
# TypeError: %d format: a real number is required, not list
这里本意是打印条目数量,正确的写法应该是 % len(items)。
第二类,百分号被当成占位符。字符串里写了字面的 %,例如:
print("今日涨幅 +3%" % rate)
# TypeError: not all arguments converted during string formatting
Python 把 +3% 里的 % 当成了格式化占位符,而后面又跟了参数,于是语法上对不上。
第三类,把日期串直接转整数:
day = int("2026-09-07")
# ValueError: invalid literal for int() with base 10: '2026-09-07'
第四类,字符串里嵌套了同一种引号,直接语法错误:
sql = 'SELECT * FROM t WHERE name = 'abc''
# SyntaxError: invalid syntax
第五类,某天恰好没有样本,除法除零:
avg = total / count
# ZeroDivisionError: division by zero
根因
这五类看着零散,其实都指向同一个根源:在"快速写脚本"的心态下,把人类语言的直觉直接翻译成了代码,而 Python 对类型、对占位符、对字符边界的规则是严格的。
- 格式化占位符(
%d/%s)对参数类型有硬性要求,%d只接受一个数值,不能喂整个容器。 - Python 的
%运算符承担了两种含义:字符串格式化运算符,和数值取模。写在字符串里又不转义时,解释器优先按"格式化"理解。 int()只认纯数字字符串,不会替你剥掉连字符。日期是"带分隔符的字符串",不是"整数"。- 引号在词法分析阶段就划定了字符串边界。内外都用单引号时,解释器看到第一个内层单引号就认为字符串结束了。
- 除法的分母来自"当天的样本数",而样本数在某些日期可能为零——这是数据层面的边界,不是代码逻辑能想当然的。
解决
逐个对照修:
格式化一律改用 f-string,它的语义清楚得多,也不用担心 % 被误读:
print(f"共 {len(items)} 条")
print(f"今日涨幅 +3%")
如果因为某些原因必须用 % 格式化,就把字面百分号写成 %% 转义。
日期先规范化再转数值:如果确实需要把 2026-09-07 变成一个可比较的整数(比如 20260907),先去掉分隔符:
day = int("2026-09-07".replace("-", "")) # 20260907
嵌套引号换成不同的引号类型,外层双引号、内层单引号,或者用参数化查询彻底避免手工拼 SQL:
sql = "SELECT * FROM t WHERE name = 'abc'"
关键路径加防御性判断,分母为零时返回空值或默认值,而不是让它崩:
avg = total / count if count else 0
延伸与预防
这类错误的高频点其实非常固定:类型、格式化、日期、引号、除零。值得在写代码的时候就形成条件反射——
- 看到
%格式化时,先想一下字符串里有没有字面的百分号; - 看到
int()/float()时,先确认输入是不是纯数字串; - 看到字符串里要嵌引号时,主动换一种引号;
- 看到除法时,先问一句"分母会不会是 0"。
更进一步,把"一个人维护一堆脚本"这种场景配上最低限度的自动化:对核心函数写几个 assert 或小测试、脚本入口处加 python -W error 让警告暴露出来、用 ruff / flake8 这类工具做静态检查——它们能挡住相当一部分低级错误,比事后一个个翻日志要省事得多。