登录
首页 >  文章 >  python教程

Python argparse 命令行工具实战:子命令、参数校验和配置合并

来源:17golang原创

时间:2026-06-13 08:18:37 241浏览 收藏

很多 Python 脚本一开始只是临时工具,参数直接写在代码里。用久之后,问题就来了:路径要改、天数要改、是否只预览也要改,最后变成“改一行代码再跑一次”。这时就该把脚本升级成命令行工具。

argparse 是 Python 标准库里的命令行参数解析模块,不需要额外安装。它适合做内部运维脚本、数据处理脚本、批量任务工具,也适合把零散脚本整理成团队可复用的小工具。

摘要

本文用“日志清理工具”做例子,从基础参数解析开始,逐步加入类型校验、布尔开关、子命令、配置文件默认值和环境变量覆盖。最后得到一个更稳的命令行入口:参数来源清楚、错误提示友好、运行前能先预览。

适合人群

适合写过 Python 脚本,但还没有系统整理命令行参数的开发者。你只需要会写函数、理解列表和字典,就能跟着示例改造自己的脚本。

目录

  • 从 argv 到 argparse 解析结果
  • 给参数加类型、默认值和开关
  • 用子命令拆分不同动作
  • 配置文件和环境变量如何合并
  • 常见坑和上线前检查

一、从 argv 到 argparse 解析结果

先看一个最小版本。我们希望通过命令行传入日志目录、保留天数,并支持只预览不删除。

import argparse
from pathlib import Path


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="logtool",
        description="清理超过指定天数的日志文件",
    )
    parser.add_argument("--path", required=True, help="日志目录")
    parser.add_argument("--days", type=int, default=7, help="保留最近多少天的日志")
    parser.add_argument("--dry-run", action="store_true", help="只预览,不删除")
    return parser


def main() -> None:
    parser = build_parser()
    args = parser.parse_args()

    log_dir = Path(args.path)
    print({"path": str(log_dir), "days": args.days, "dry_run": args.dry_run})


if __name__ == "__main__":
    main()

运行示例:

python logtool.py --path ./logs --days 14 --dry-run

Python argparse 从输入参数到解析命令校验类型并得到结果的流程

这张图对应 argparse 的核心流程:用户输入参数,解析器把字符串拆成结构化字段,再根据 type 和开关规则做校验,最后交给业务代码使用。这个过程比手动解析 sys.argv 更可靠。

二、给参数加类型、默认值和开关

命令行工具最怕“看起来能跑,但参数其实不合法”。例如保留天数不能小于 1,日志目录也必须存在。可以把检查逻辑放在解析后。

from pathlib import Path


def validate_args(args: argparse.Namespace) -> None:
    log_dir = Path(args.path)

    if not log_dir.exists():
        raise SystemExit(f"日志目录不存在: {log_dir}")

    if not log_dir.is_dir():
        raise SystemExit(f"不是目录: {log_dir}")

    if args.days 

再把校验接到 main 里:

def main() -> None:
    parser = build_parser()
    args = parser.parse_args()
    validate_args(args)

    print("参数检查通过")

argparse 负责语法层面的解析,比如整数、开关、必填项;业务层面的规则,比如目录是否存在、天数是否合理,建议单独放在校验函数里。这样以后测试和维护都更轻松。

三、用子命令拆分不同动作

当工具动作变多以后,不要把所有参数都塞在一个入口里。比如日志工具可能有三个动作:scan 只扫描,clean 清理,report 输出统计。这时可以用子命令。

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="logtool")
    subparsers = parser.add_subparsers(dest="command", required=True)

    scan = subparsers.add_parser("scan", help="扫描可清理文件")
    scan.add_argument("--path", required=True)
    scan.add_argument("--days", type=int, default=7)

    clean = subparsers.add_parser("clean", help="清理过期文件")
    clean.add_argument("--path", required=True)
    clean.add_argument("--days", type=int, default=7)
    clean.add_argument("--dry-run", action="store_true")

    report = subparsers.add_parser("report", help="输出日志统计")
    report.add_argument("--path", required=True)

    return parser

子命令的好处是边界清楚。扫描命令不需要删除开关,统计命令不需要保留天数,每个动作只暴露自己真正需要的参数。

python logtool.py scan --path ./logs --days 7
python logtool.py clean --path ./logs --days 30 --dry-run
python logtool.py report --path ./logs

四、配置文件和环境变量如何合并

命令行参数适合临时覆盖,但固定配置不适合每次都敲。例如默认日志目录、默认保留天数,可以放到 JSON 配置里;敏感信息或部署环境差异,可以从环境变量读取。

Python argparse 子命令读取配置并用环境变量覆盖后运行任务的流程

推荐合并顺序是:代码内置默认值

{
  "path": "./logs",
  "days": 14
}

读取配置并合并:

import json
import os
from dataclasses import dataclass
from pathlib import Path


@dataclass
class CleanOptions:
    path: str
    days: int
    dry_run: bool


def load_config(file_path: str | None) -> dict:
    if not file_path:
        return {}

    path = Path(file_path)
    if not path.exists():
        raise SystemExit(f"配置文件不存在: {path}")

    return json.loads(path.read_text(encoding="utf-8"))


def merge_clean_options(args: argparse.Namespace) -> CleanOptions:
    config = load_config(getattr(args, "config", None))

    path = args.path or os.getenv("LOGTOOL_PATH") or config.get("path") or "./logs"
    days = args.days or int(os.getenv("LOGTOOL_DAYS", config.get("days", 7)))

    return CleanOptions(
        path=path,
        days=days,
        dry_run=bool(args.dry_run),
    )

注意这里用了 args.path or ...,所以参数定义里要允许不传路径:

clean.add_argument("--config", help="配置文件路径")
clean.add_argument("--path", help="日志目录")
clean.add_argument("--days", type=int, help="保留最近多少天的日志")
clean.add_argument("--dry-run", action="store_true")

这套合并规则能让工具更适合不同环境:本地调试可以直接传参数,服务器上可以放配置文件,流水线里可以用环境变量覆盖。

五、把业务函数接进来

最后把解析结果交给真正的业务函数。示例里为了安全,只打印将要处理的文件,不直接删除。

from datetime import datetime, timedelta


def scan_old_logs(options: CleanOptions) -> list[Path]:
    log_dir = Path(options.path)
    deadline = datetime.now() - timedelta(days=options.days)
    matched: list[Path] = []

    for item in log_dir.glob("*.log"):
        modified = datetime.fromtimestamp(item.stat().st_mtime)
        if modified  None:
    files = scan_old_logs(options)

    for file_path in files:
        if options.dry_run:
            print(f"[预览] 将清理: {file_path}")
        else:
            file_path.unlink()
            print(f"已清理: {file_path}")

入口分发可以保持简单:

def main() -> None:
    parser = build_parser()
    args = parser.parse_args()

    if args.command == "clean":
        options = merge_clean_options(args)
        validate_clean_options(options)
        run_clean(options)
    elif args.command == "scan":
        print("扫描模式可以复用 scan_old_logs")
    elif args.command == "report":
        print("统计模式可以读取目录后输出数量和大小")

常见坑

1. 布尔参数不要写成 type=bool

type=bool 很容易得到反直觉结果。布尔开关建议使用 action="store_true"action="store_false"

2. required 不要滥用

如果参数可以来自配置文件或环境变量,就不要在命令行层面设置 required=True。否则还没来得及合并配置,解析阶段就已经失败了。

3. 错误提示要告诉用户怎么改

不要只输出“参数错误”。更好的提示是“日志目录不存在: ./logs”,或者“--days 必须大于等于 1”。这类提示能直接指向下一步操作。

上线前检查清单

  • --help 是否能说明每个参数的含义。
  • 默认值是否安全,尤其是清理、覆盖、删除类工具。
  • 是否提供 --dry-run 预览模式。
  • 参数来自命令行、配置文件、环境变量时,优先级是否清楚。
  • 错误提示是否能让使用者知道下一步怎么修。

总结

argparse 不只是把字符串变成变量,它更像命令行工具的入口规范。先用它解析参数,再用校验函数保证业务规则,复杂动作交给子命令,固定配置放到文件和环境变量里。这样写出来的 Python 工具,既适合自己反复使用,也适合交给团队同事运行。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>