Python 新版本的内置函数太香了!
CSV 导进去少了 37 行,脚本没报错,库里数据也“看着正常”。
这种问题我现在第一眼不怀疑 pandas,不怀疑数据库,先看这类代码:
for user_id, amount in zip(user_ids, amounts):
save_bill(user_id, amount)
老版本这么写很顺手,也很坑。
zip() 默认按最短的那组数据停。user_ids 有 1000 个,amounts 只有 963 个,它不吭声,剩下 37 个直接被你丢在半路。
现在我一般这么写:
defbuild_bill_rows(user_ids, amounts, batch_no):
rows = []for uid, amount in zip(user_ids, amounts, strict=True):
rows.append({
"batch_no": batch_no,
"user_id": uid,
"amount": int(amount),
})
return rows
这地方加 strict=True,不是为了代码好看,是为了让脏数据早点炸。
Python 3.10 给 zip() 加了 strict 参数,长度不一致会抛 ValueError,这个改动就是专门治“静默丢数据”的。官方 PEP 里也提到,默认 zip() 很容易让逻辑错误悄悄吞掉数据。
我挺喜欢这种改动。
不是多一个花活,而是少一次事故。
还有一个是 Python 3.14 里的 map(strict=True)。官方文档里 map() 已经带了 strict=False 参数,3.14 开始可以检查多个 iterable 长度是否一致。
以前清洗字段,有人喜欢这么写:
names = ["sku", "price", "stock"]
values = ["A102", "19.8"]row = dict(map(lambda x, y: (x, y), names, values))
这代码我看见就不太放心。
字段和值对不上,脚本还能跑,这才麻烦。改一下:
defpack_import_row(headers, values, line_no):
try:
return dict(map(lambda k, v: (k.strip(), v.strip()), headers, values, strict=True))
except ValueError as e:
raise RuntimeError(f"第 {line_no} 行字段数量不对: {e}") from e
这种代码不优雅,但现场好用。
导入脚本最怕的不是报错,最怕的是不报错还写进库。报错最多重跑,不报错就是后面查账、补数、扯皮。
异步这块也有两个内置函数容易被忽略:aiter() 和 anext()。它们是 Python 3.10 加进来的,官方文档里也写得很清楚,aiter() 拿异步迭代器,anext() 相当于异步版 next(),还可以给默认值。
比如一个接口分页拉日志,很多人写一坨 async for,只为了拿第一条异常记录:
asyncdeffirst_bad_record(page_stream):
asyncfor item in page_stream:
if item.get("status") == "FAILED":
return item
returnNone
我现在更愿意拆薄一点:
asyncdeffailed_records(page_stream):
asyncfor page in page_stream:
for row in page["records"]:
if row.get("status") == "FAILED":
yield rowasyncdefpick_first_failed(page_stream):
record_iter = aiter(failed_records(page_stream))
returnawait anext(record_iter, None)
这段代码看着多了几行,但逻辑清楚。
分页归分页,筛选归筛选,拿第一条归拿第一条。后面要改成“拿前 10 条失败记录”,不用推翻重写。
还有个小改动我也常用:isinstance() 支持联合类型。
以前判断配置值,代码长这样:
ifnot isinstance(timeout, (int, float)):
raise TypeError("timeout 必须是数字")
现在可以写成这样:
defread_timeout(conf):
timeout = conf.get("timeout", 3)ifnot isinstance(timeout, int | float):
raise TypeError(f"timeout 配错了: {timeout!r}")
return max(1, float(timeout))
Python 3.10 之后,isinstance() 的 classinfo 可以接收联合类型。
这东西谈不上多震撼,但它让代码少一层括号,也少一点“老 Python 味”。
我对内置函数的态度一直很简单:能不用第三方库就别急着上库,能让错误提前暴露就别装宽容。
尤其是数据导入、账单生成、日志清洗、异步拉取这种脚本,代码一般不长,但杀伤力不小。
zip(strict=True) 和 map(strict=True) 这种东西,平时看着就一行参数。真出问题的时候,这一行就是报警器。
我宁愿脚本凌晨 2 点直接失败,也不想第二天下午有人拿着 Excel 来问:这几十条数据去哪了?