Administrator
Administrator
发布于 2026-10-31 / 0 阅读
0
0

Python 从入门到初级(18):命令行与 logging:把脚本做成工具

本篇是《Python 从入门到初级》第 18 / 20 篇。示例使用 Python 3.12 兼容语法,完整目录结构与数据见配套源码包。

能运行的脚本,还不一定是好用的工具。使用者不应该每次打开代码改输入路径;调度系统也需要知道成功还是失败。本篇为订单模块增加命令行参数、日志和退出码。

命令行参数驱动报表模块,日志与结果分开输出

1. 一个小工具应该有什么接口

本例接收 --input、--output,提供 --dry-run 预览和 --verbose 调试日志。默认输入使用脚本旁的教学 CSV,输出必须由使用者明确指定。

正常统计结果以 JSON 写到标准输出,运行日志写到标准错误。这样别人可以接收 JSON,而不必从“开始处理……”这样的文字里提取数据。

2. 完整入口:report_cli.py

保留 ops_tools 包,在示例根目录运行:

import argparse
import csv
import json
import logging
from pathlib import Path
from ops_tools.orders import read_orders, save_json, summarize


def main():
    base = Path(__file__).resolve().parent
    parser = argparse.ArgumentParser(description="统计订单 CSV,金额单位为整数分")
    parser.add_argument("--input", type=Path, default=base / "data" / "orders.csv")
    parser.add_argument("--output", type=Path, required=True, help="新的 JSON 文件;存在时拒绝覆盖")
    parser.add_argument("--dry-run", action="store_true", help="仅计算和展示,不创建输出文件")
    parser.add_argument("--verbose", action="store_true")
    args = parser.parse_args()
    logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO,
                        format="%(levelname)s %(message)s")
    try:
        logging.debug("输入路径:%s", args.input.resolve())
        orders = read_orders(args.input)
        summary = summarize(orders)
        if args.dry_run:
            logging.info("预览完成,没有创建输出文件")
        else:
            save_json(summary, args.output)
            logging.info("报告已保存:%s", args.output.resolve())
        print(json.dumps(summary, ensure_ascii=False, indent=2))
    except (OSError, ValueError, csv.Error) as exc:
        logging.error("处理失败:%s", exc)
        return 1
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

argparse 会解析参数、生成帮助,并检查必需选项。logging 让程序按 DEBUG、INFO、ERROR 等级输出记录;这些级别是日志的分类,不自动等同于监控告警。

机制见 argparse 文档 与 logging 文档。

3. 先看帮助,再预览

python report_cli.py --help
python report_cli.py --output output/first-report.json --dry-run

预期显示总订单 5、已支付 3、金额 38300 分,提示完成预览,但没有创建输出文件。dry-run 仍会读取和验证输入,因此缺少文件或数据非法时仍可能失败。

--output 即使预览时也必需,这是本工具明确的参数规则。换一个工具,规则可能不同,所以先读帮助而不是猜选项。

4. 实际生成,再故意重复一次

python report_cli.py --output output/first-report.json

第一次预期成功创建文件。原样再执行一次,预期因文件已存在而失败。选择一个新的输出文件名可以继续生成,不需要把保护已有结果的行为当成程序错误。

输入与输出的相对路径按当前工作目录解释;只有默认输入明确基于脚本目录。脚本不会把你提供的所有相对路径都自动改成它自己的目录。

5. 退出码是给调用者的结果

退出码 本工具约定
0 统计和要求的输出完成
1 文件或数据处理失败
2 命令参数不合法,由 argparse 报告

Linux/macOS 可在执行后立即用 echo $? 查看;PowerShell 使用 $LASTEXITCODE。它们都是外层 Shell 提供的状态,不能把不同 Shell 的写法随意混用。

Jenkins 普通 sh 步骤会把非零退出码视为失败;如果外层把状态忽略,正确输出错误的脚本也可能被误报为成功。

6. 日志应该记录什么

记录阶段、输入输出位置、数量和失败原因,通常比打印完整业务数据更合适。示例的 verbose 会显示输入路径,但不会逐行打印全部订单。

DEBUG 不意味着应该输出密码、令牌或完整敏感数据。正式使用时还要考虑日志轮转、集中收集和使用权限,这些并不由 basicConfig 自动完成。

7. 练习与参考结果

不提供 output,预期参数错误;提供不存在的 input,预期处理失败;使用新 output,预期成功;给原始 CSV 加重复订单,预期被模块拒绝。

尝试把标准输出保存成另一个文件,并确认其中仍是可解析 JSON。不要把这个重定向文件与 --output 指向同一文件,因为外层 Shell 可能先创建它,触发工具的拒绝覆盖规则。


上一篇:文件整理实战:先预览,再按类型复制 · 系列目录 · 下一篇:自动化测试:保护报表与文件整理规则


评论