Python技术迷

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 row

asyncdefpick_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 来问:这几十条数据去哪了?