Python 新版本的内置函数太香了!
线上脚本一跑歪,我现在第一反应已经不是先翻业务逻辑了,而是先看你机器上的 Python 版本。这个习惯不是玄学,是真被新版本教育过。
有些同学还停在 for、if、try except 那套老写法里,代码也能跑,就是写着累,查着更累。尤其是那种导文件、扫日志、拉接口、清洗数据的小脚本,明明十几行能收住,最后硬是写成四五十行。问题不在业务,问题在你还没把新版本内置的东西用起来。
我这两年用得最顺手的,不是什么花里胡哨的第三方库,反而是 Python 自己给的这几样。
先说 breakpoint()。
以前排脚本问题,最烦的是手动塞 print。今天打印这个变量,明天打印那个列表长度,改完还得删。删漏一行,提交上去就是事故。
现在我排导入脚本,基本都是直接断。
defload_user_rows(path: str) -> list[dict]:
rows = []
with open(path, "r", encoding="utf-8") as f:
for lineno, line in enumerate(f, start=1):
parts = line.rstrip("\n").split(",")
if len(parts) != 4:
breakpoint() # 到这先别猜,直接断进来看脏数据
rows.append({
"uid": parts[0],
"name": parts[1].strip(),
"phone": parts[2].strip(),
"dept": parts[3].strip(),
})
return rows
这个东西看着朴素,真排现场数据的时候比 print 舒服太多。尤其是那种“就一条数据有问题”的场景,断进去看上下文,比你翻半天日志快。很多人嫌它简单,我倒觉得这种简单才值钱。
再说 zip(strict=True)。
这玩意虽然不是新增函数,但新版本这个参数我是真喜欢。因为很多数据错位问题,代码表面上根本不报错,它只会悄悄把短的那边截断,然后你以为自己处理成功了。
比如我之前写一个字段映射脚本,表头和行数据一旦长度对不上,老写法就埋雷。
defbuild_record(columns: list[str], values: list[str]) -> dict:
return {k: v for k, v in zip(columns, values, strict=True)}
这个 strict=True 很顶。长度不一致直接炸,别在那里跟我装成功。
很多脏数据就是这么混进系统的。你不用这个参数,最后 ES 里少字段、导出文件列错位、下游拿到半截数据,全是后账。老程序员有个习惯:能在入口处炸掉,就别等线上慢慢烂。
然后是 aiter() 和 anext()。
这两个刚出来的时候,不少人觉得没啥存在感。真写异步消费、异步分页拉取、异步流式处理的时候,你就知道它顺手了。以前得自己包一层,现在内置给了,代码明显干净一点。
比如我写一个异步分页拉取接口数据的逻辑,先拿第一页试探,不对就停,不用先整坨拉完。
classPageStream:
def__init__(self, fetcher):
self.fetcher = fetcher
self.page_no = 1def__aiter__(self):
return self
asyncdef__anext__(self):
data = await self.fetcher(self.page_no)
ifnot data:
raise StopAsyncIteration
self.page_no += 1
return data
asyncdefload_first_abnormal(fetcher):
stream = aiter(PageStream(fetcher))
first_page = await anext(stream, [])
return [x for x in first_page if x.get("status") == "abnormal"]
这种写法的好处,不是“语法新”,而是你在处理异步流的时候,终于不用老想着怎么手搓控制逻辑了。该停停,该拿拿,读代码的人也不费劲。
还有个我觉得被低估的,是 BaseExceptionGroup 配合 except* 这套异常处理。严格说不只是函数层面的事,但在并发任务里特别有用。以前批量调用接口,十个任务里挂三个,报错常常拧成一团。现在分组处理,定位快很多。
import asyncioasyncdefcall_one(uid: int):
if uid % 3 == 0:
raise TimeoutError(f"uid={uid} timeout")
if uid % 5 == 0:
raise ValueError(f"uid={uid} bad payload")
return {"uid": uid, "ok": True}
asyncdefbatch_check():
try:
asyncwith asyncio.TaskGroup() as tg:
for uid in range(1, 11):
tg.create_task(call_one(uid))
except* TimeoutError as eg:
for e in eg.exceptions:
print("超时任务:", e)
except* ValueError as eg:
for e in eg.exceptions:
print("脏数据任务:", e)
这个东西在批量任务里很有味道。你不用再从一坨异常日志里肉眼分拣,到底是超时、参数问题,还是第三方返回烂数据。该按类归类就归类,排查顺序立刻清楚。
最后再补一个小习惯:版本新了以后,python -m pdb 我反而用少了,breakpoint() 用多了;手写异步迭代控制少了,aiter()、anext() 用多了;静默吞错位数据少了,zip(strict=True) 用多了。
这些变化看着都不大,但写脚本的人知道,真正省时间的从来不是“大功能”,而是这种小地方不别扭了。
所以别老觉得升级 Python 只是为了跟风。很多时候你脚本又臭又长,不一定是你不会写,可能只是你还停在老版本的手感里。版本一旧,写法也跟着旧,最后排障思路都会变钝。