有着两百行的数据导入脚本, 于这脚本内容当中, 光是路径去执行拼接操作参数会进行校验处理, 添加日志进行打印, 还有文件实施关闭动作, 这几方面就占据了七八十行的篇幅。这样的代码, 其功能不怎么复杂, 然而维护起来却格外麻烦。改动一回目录, 会遗漏两个反斜杠增添一个字段, 初始化逻辑分散于三个位置异常分支一旦增多, close究竟有没有执行都得依靠猜测。当下我着手撰写此种脚本时, 首件事并非去安装第三方包, 而是先行翻阅标准库。接下来所列的这8个, 着实能够削减相当多的重复代码。1. 别再手拼文件路径这种写法我看到就头疼report_file base_dir / day /failed/ batch_no .json嗯Linux之上或许是没什么事儿的, 然而一旦切换到, 那个目录的末尾, 要多出一个斜杠来, 立刻, 就会开始让人感觉别扭了。直接按路径对象处理from pathlib import Path work_dir Path(/data/import) / 2026-07-22 failed_file work_dir / failed / batch_1042.json failed_file.parent.mkdir(parentsTrue, exist_okTrue) failed_file.write_text({status: failed}, encodingutf-8)建构目录, 判别文件, 读取所得内容, 把后缀予以更改这般种种操作, 全部集中于同一个对象之上。在脚本当中, 只要出现三次路径进行拼接的情况, 我通常就会将其替换掉。2. 少写一堆初始化代码输入数据之际, 常常得去界定一个临时的对象。亲手书写, 顿号、逗号, 一旦字段产生变更, 好些地方都会随之变动。from dataclasses import dataclass, field dataclass(slotsTrue) classImportRow: order_no: str amount: int source: str manual errors: list[str] field(default_factorylist) property defvalid(self) - bool: returnnot self.errors在这儿的,需要留意,切忌直接书写 .至于共享可变对象这个陷阱,当代码简短之时反倒更为隐蔽呢。3. 别到处判断 key 存不存在按门店归类失败订单最笨的写法是if shop_code notin failed_orders: failed_orders[shop_code] failed_orders[shop_code].append(order_no)换成 from collections import defaultdict failed_orders defaultdict(list) for row in import_rows: if row.errors: failed_orders[row.source].append(row.order_no)不是存在少两行这般情况的问题, 分支已没有了, 后续增添统计逻辑之际, 不容易出现把初始化写遗漏的状况。在同一个模块以内的, 同样是具备良好使用特性的。针对统计错误的类型而言, 并不需要依靠自身去进行累加器的维护操作:from collections import Counter error_count Counter( error for row in import_rows for error in row.errors )4. 批量处理别重复切片批量写数据库时很多代码都在重复造“分批函数”。for start in range(0, len(records), 500): batch records[start:start 500] save_batch(batch)3.12 可以直接用 . from itertools import batched for batch in batched(records, 500): save_batch(batch)这里, 我会多瞧一下版本, 线上倘若还是旧版本, 那就别为了少几行代码, 偷偷去复制一个同名的实现, 不然后面的人会误以为它就是标准库呢。5. 缓存和函数适配别自己造轮子配置查询, 这类函数, 参数相同, 结果也 same, 还有规则加载, 却在一批数据里反复执行。from functools import lru_cache lru_cache(maxsize256) defload_shop_rule(shop_code: str) - dict: return query_rule_from_db(shop_code)此等缓存适宜于稳定的微小结果, 若返回几十兆的数据, 且内容频繁发生改变, 还往其上面套, 那么这并非是优化, 而是给内存埋下隐患, 是这样的情况, 没错吧。也能处理重复参数from functools import partial write_audit partial( save_event, event_typeorder_import, operatorscheduler ) write_audit(order_noA1024, resultsuccess)6. 资源释放别靠 撑着很多人会写那一层又一层的 try/ , 用于临时切换目录, 用来打开资源, 还要为之加锁。自定义上下文后调用处会干净很多from contextlib import contextmanager from time import perf_counter contextmanager deftrace_cost(task_name: str): started perf_counter try: yield finally: cost perf_counter - started print(f{task_name} cost{cost:.3f}s) with trace_cost(validate-import-file): validate_file(/data/inbox/orders.csv)相较于其他地方, 我更倾向于将计时, 以及锁释放和临时文件清理放置于此。通过这种方式, 业务代码中的缩进少了一层, 在排查异常情况时, 能让人感觉舒服许多。7. 命令行参数不要手撸 sys.argv通常一开始定时脚本有着的是唯独一个参数, 往后日期会被增添, 批次会被增添, 是否重跑也会被增添。持续顺着数组下标去取值, 很快这种东西便会出现:retry sys.argv[3] 1换成 校验和帮助信息一起解决import argparse parser argparse.ArgumentParser parser.add_argument(--day, requiredTrue) parser.add_argument(--batch-size, typeint, default500) parser.add_argument(--retry-failed, actionstore_true) args parser.parse_args参数写错时直接报清楚不用自己维护十几个 if 。8. 别再复制 print 模板刚写脚本来使用时, 运用 print是没有问题的。等到它进入了定时任务之后, 依旧在不断打印“执行成功”这样的字样, 而当出现问题的时候, 基本上是查找不到批次的, 查不到订单号的, 同时也查不到异常栈的。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(message)s ) logger logging.getLogger(order_import) try: imported run_import(args.day, args.batch_size) logger.info(import finished day%s rows%d, args.day, imported) except Exception: logger.exception(import failed day%s, args.day) raise将日志参数写成提前组合成 f-的写法不可取。当日志级别处于关闭状态时, 先计算字符串的情况依旧会发生。少量时难以察觉, 可一旦在循环里打印几万次, 那情况就大不一样了。这八个库, 既谈不上新, 也说不上炫。真正具备实用价值之处, 在于将路径、对象初始化、分组、分批、缓存、资源释放、参数解析以及日志这些处于边缘位置的代码收纳回来。业务代码只剩业务判断脚本才不容易越改越厚。