使用python制作CLI基础_1

本文最后更新于:2025年2月5日 下午

主要学习使用python制作CLI两个主要方面的内容:

  1. 利用toml文件打包python项目
  2. python中的命令行参数解析 (本篇内容)

 

本文主要介绍三种较为流行的命令行解析方式,分别为:

  • sys.argv
  • argparse
  • click

三者各有优劣且已经能够满足绝大部分人的需要

以下内容均假设python项目已经被打包为名为 mycli 的命令行

 

一、sys.argv

sys 库是python自带的一个库,不需要额外安装

import sys
args = sys.argv

print("Argument List:", str(args))

在上述代码的前提下,假设我们在终端输入是

mycli input.txt -o test.txt --verbose

代码的输出结果即为

["mycli", "input.txt", "-o", "test.txt", "--verbose"]

sys.argv 即为获取终端命令行的全部输入并且采用 split 使用空格分割为list

 

优点:

  • 简洁方便,不需要太多代码即可解析获取参数
  • 不用额外安装

缺点:

  • 参数以list形式解析获取,代码后续处理会较为复杂,需要重新挑选类型检查等
  • 返回的每个元素固定是 str ,可能需要强制类型转换
  • 功能较少,没有默认值设置等能力

 

二、argparse

argparse 是十分实用且流行的用于解析命令行参数的包

它的功能十分强大,可以设置默认值也可以进行类型转化与判断

同时,利用它,还可以实现 git clone 一样的子命令的功能

import argparse

def main():
    # 1. 创建解析器对象,设置描述信息
    parser = argparse.ArgumentParser(
        description="示例:演示 argparse 的常用功能",
        epilog="更多用法请参考官方文档: https://docs.python.org/3/library/argparse.html"
    )

    # 2. 添加通用参数 ---------------------------------------------
    
    # 位置参数(必须提供的参数)
    parser.add_argument(
        "filename",  # 参数名称(位置参数不需要前缀)
        type=str,    # 参数类型
        help="输入文件的路径"  # 帮助信息
    )

    # 可选参数(短选项和长选项)
    parser.add_argument(
        "-v", "--verbose",  # 短选项和长选项,如果两者同时存在,默认情况下后续使用长选项即verbose访问
        action="store_true",  # 存储为布尔值(存在则为 True,不存在则为 False)
        help="启用详细输出模式"
    )

    # 带类型的参数 + 默认值
    parser.add_argument(
        "--num",
        type=int,
        default=10,
        choices=range(1, 100),  # 限制取值范围
        help="指定一个数字 (默认: %(default)s)"
    )

    # 互斥参数组(不能同时使用的参数)
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "--start",
        action="store_true",
        help="启动服务"
    )
    group.add_argument(
        "--stop",
        action="store_true",
        help="停止服务"
    )

    # 多值参数(接受多个值)
    parser.add_argument(
        "--values",
        nargs="+",  # 接受至少一个值
        type=float,
        help="传入多个浮点数(例如 1.1 2.2 3.3)"
    )

    # 版本参数(固定格式)
    parser.add_argument(
        "-V", "--version",
        action="version",
        version="%(prog)s 1.0" 
    )

    # 3. 添加子命令 ---------------------------------------------
    subparsers = parser.add_subparsers(
        title="子命令",  # 子命令标题
        dest="command",  # 存储子命令名称的属性
        help="可用子命令"
    )

    # 子命令 1: process
    parser_process = subparsers.add_parser(
        "process",
        help="处理文件"
    )
    parser_process.add_argument(
        "--output",
        required=True,  # 必选参数
        help="输出文件路径"
    )
    parser_process.add_argument(
        "--force",
        action="store_true",
        help="覆盖已存在的文件"
    )

    # 子命令 2: clean
    parser_clean = subparsers.add_parser(
        "clean",
        help="清理文件"
    )
    parser_clean.add_argument(
        "--recursive",
        action="store_true",
        help="递归清理子目录"
    )
    parser_clean.add_argument(
        "--dry-run",
        action="store_true",
        help="模拟运行(不实际删除)"
    )

    # 4. 解析参数并处理结果 --------------------------------------
    args = parser.parse_args() #默认获取sys.argv[1:]进行处理

    # 打印所有参数(演示用)
    print("\n解析后的参数:")
    for key, value in vars(args).items():
        print(f"{key:>10}: {value}")

    # 示例逻辑分支
    if args.command == "process":
        print(f"\n处理文件: {args.filename} -> {args.output}")
        if args.force:
            print("警告:已启用强制覆盖模式")
    elif args.command == "clean":
        print(f"\n清理文件: {args.filename}")
        if args.recursive:
            print("递归清理子目录")

if __name__ == "__main__":
    main()

以上样例代码包含了 argparse 比较常用的各种参数情形

并且,当你输入 --help 或者 -h 时,它还会自动根据 help 的相关内容生成帮助文档显示在终端

从上述代码可以看出,argparse 功能十分强大,已经基本满足了开发者的需求

argparse 解析参数的步骤也十分清晰

  1. 使用 argparse.ArgumentParser 创建解析器
  2. 利用 add_argument , add_subparsers ,add_parser 等命令对解析器进行添加参数,子命令集合,子命令等操作
  3. 解析器调用 parser_args 进行解析
  4. 解析结果通过与之前添加的参数名字一一对应进行调用

 

优点:

  • 自带,无需额外下载
  • 功能强大,逻辑清晰

缺点:

  • 对于嵌套命令实现还是比较混乱与复杂

 

三、click

其实 argparse 已经是满足绝大部分人的需求了,代码方面也并不复杂。

click 则是更加强大的用于制作命令行的工具,它不仅通过装饰器使得代码更加易懂,还支持彩色输出,prompt,文件类型检查等其他功能。

但与此同时,更强大的功能也就带来了更加昂贵的学习成本,所幸 click 的官方文档十分条理清晰,下文只是极其简单的小引子,更多功能学习请参考官方文档

与前两者不同,click 是第三方库因此使用前需要pip install安装

1.无参数命令

import click

@click.command()
def hello():
    click.echo('Hello World!') #echo函数效果等同于print,但是对环境更加包容且更加健壮
    
if __name__ == '__main__':
    hello()

此时当我们调用 mycli ,假设设置程序入口为 hello()

终端应该输出 Hello World!

2.嵌套命令

2.1 采用add_command与自定义子命令名称

@click.group()
def cli():
    click.echo("Welcome!")

@click.command()
def initDb():
    click.echo('Initialized the database')

@click.command(name="drop") #通过name指定子命令为drop,默认情况下为全部小写的函数名
def dropdb():
    click.echo('Dropped the database')

cli.add_command(initdb)
cli.add_command(dropdb)

此时当我们调用 mycli ,假设程序入口为 cli()

终端输入 终端输出
mycli click自动生成的help界面
mycli initdb Welcome!
Initialized the database
mycli drop Welcome!
Dropped the database

在此情况下,可以先写子命令的实现函数再指定父命令入口,即 initDb 或者 dropdb 函数可以定义在 cli 函数之前

2.2 不使用add_command与激活父命令

在 2.1 的代码情况下,我们发现只输入 mycli 是只会显示help界面,哪怕里面有逻辑操作

这是 click 的默认设置,但有时我们也希望只输入父命令时进行一些操作,我们可以通过设置 invoke_without_command=True

import click

@click.group(invoke_without_command=True) # 在此设置下父命令单独输入也会执行该函数内容
@click.pass_context
def mycli(ctx):
    """Main command."""
    # 判断是否跟着子命令
    if ctx.invoked_subcommand is None:
        click.echo("Running default behavior of mycli")

@mycli.command() # 注意这里不是click.command,这样就不需要后续调用add_command函数
def init():
    """Initialize something."""
    click.echo("Initialized the database")

@mycli.command()
def end():
    """End something."""
    click.echo("Ended the process")
终端输入 终端输出
mycli Running default behavior of mycli
mycli init Initialized the database
mycli end Ended the process

3.添加参数

import click

@click.command()
@click.argument('input_file', type=click.Path(exists=True))  # 位置参数,必须存在该文件
# click.Path参数可以对文件各方面检查,例如必须存在,可写入,不可以是目录等
														
@click.option('--output', '-o', default='output.txt',  # 选项参数,短参数和长参数形式
              help='输出文件路径,默认为output.txt')
@click.option('--processes', '-p', type=int, default=1,  # 类型转换和默认值
              help='并发进程数,默认为1')
@click.option('--verbose', '-v', is_flag=True,  # 布尔标志参数
              help='启用详细输出模式')
@click.option('--enable-feature', 'feature_flag',  # 参数别名映射到变量名
              flag_value=True, default=False,
              help='启用某个功能')
@click.option('--color', type=click.Choice(['red', 'green', 'blue']),  # 限定可选值
              help='颜色选择(red/green/blue)')
@click.option('--debug/--no-debug', default=False,  # 互斥选项
              help='启用或禁用调试模式')
@click.option('--hidden-option', hidden=True,  # 隐藏参数(不在帮助信息显示)
              help='这是一个隐藏参数')
@click.option('--sizes', nargs=3, type=int,  # 接收多个参数值(这里固定3个整数)
              help='输入三个尺寸,例如:--sizes 10 20 30')
@click.option('--threshold', required=True, type=float,  # 必填参数
              help='必须提供的阈值参数')
def main(input_file, output, processes, verbose, feature_flag, 
         color, debug, hidden_option, sizes, threshold):
    """
    一个演示click用法的示例程序
    """
    # 打印所有参数值用于演示
    click.echo(f"输入文件: {input_file}")
    click.echo(f"输出文件: {output}")
    click.echo(f"进程数: {processes}")
    click.echo(f"详细模式: {'开启' if verbose else '关闭'}")
    click.echo(f"功能开关: {'开启' if feature_flag else '关闭'}")
    click.echo(f"颜色选择: {color}")
    click.echo(f"调试模式: {'开启' if debug else '关闭'}")
    click.echo(f"隐藏参数值: {hidden_option}")
    click.echo(f"尺寸参数: {sizes}")
    click.echo(f"阈值: {threshold}")

    # 这里可以添加实际的业务逻辑...

if __name__ == '__main__':
    main()

 

优点:

  • 采用装饰器,代码清楚易懂
  • 功能十分强大,prompt,彩色输出等

缺点:

  • 第三方库,需要额外下载
  • 内容较为复杂,学习成本高