本文参考这篇文章,比较了argparse、click的区别。尽管只用某一个也不是不可以,但我觉得我还是挺有必要进行一下对应的对比。

本文的目标:

本文计划实现一个带有子命令的命令行工具,同时带有全局级别的配置参数(比如数据文件地址等)。在这个前提下使用不同的标准来比较所提到的三个命令行库。

(原本计划把docopt也顺便学习一下的,但实在是没什么必要,这次就算了吧。)

下文将按照参考文章的目录进行组0织,我觉得它这个组织格式还挺有道理的

简要介绍 Link to heading

分别用三种来实现子命令下共享嵌套的情况(自己动手),均失败。 目标场景:SO上的同款问题。对于给定的子命令readwrite,父命令有一个--format参数,如何使得python main.py read --format=xxx成立。

click Link to heading

click的写法依赖于decorator

  • @click.command()声明子命令
  • @click.option('--xxx',default=x,help=xxx)@click.argument('argumentName')声明参数
  • @click.group()用来实现嵌套命令
# click.group示例,执行python main.py initdb,python main.py dropdb即可,但是--debug只能在子命令下执行
import click


@click.group()
@click.option('--debug',default=False)
def cli(debug):
    click.echo(f'Debug mode is {debug}')

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

@click.command()
def dropdb():
    click.echo('Dropped the database')

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


if __name__ == '__main__':
    cli()

argparse Link to heading

在argparse中,子命令的实现是通过add_subparsers来实现的

import argparse


def do_command_one(arg):
    print('command1', arg)
    print(arg.cmd1_option1)
    print(arg.foo)


def do_command_two(arg):
    print('command1', arg)
    print(arg.cmd1_option1)
    print(arg.cmd1_option2)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--foo', type=str)
    subparsers = parser.add_subparsers(help='Functinos')
    parser_1 = subparsers.add_parser('model', help='This is function about model')
    parser_1.add_argument('--cmd1_option1', type=str)
    parser_1.set_defaults(func=do_command_one)
    parser_2 = subparsers.add_parser('model2', help='This is function about model')
    parser_2.add_argument('--cmd2_option1', type=str)
    parser_2.set_defaults(func=do_command_two)
    args = parser.parse_args()
    if args.func:
        args.func(args)

上面这个即为我第一次学习argparse写的实例。这个脚本文件声明了两个子命令:modelmodel2model会有一个命令行参数--cmd1_option1model2会有一个命令行参数--cmd2_option1,同时全局会有一个参数--foo

此外,使用set_defaults来设置了子命令的处理函数,以应对可能需要进行单独处理的情况。

唯一的问题是,全局参数的实现比价违背一般的习惯。python main.py --foo="test" model --cmd1_option1="test2" 才能通过,如果把--foo放在子命令之后是无法识别的。

(pythonProject)  wutianyang@TIANYANGWU-MB0  ~/PycharmProjects/commandTest  python main.py model --cmd1_option1="test" --foo="123"
usage: main.py [-h] [--foo FOO] {model,model2} ...
main.py: error: unrecognized arguments: --foo=123

除此之外,这种写法只允许运行子命令的程序才能够通过编译,这个应该是写法的问题。

实现需求 Link to heading

需求本身是很简单的,最关键的一点就是子命令之间要共享部分全局参数

argparse Link to heading

SO上这个回答还挺不错的。执行python main.py create -p="db",从效果上来说确实是与预期一致。

import argparse
# Same main parser as usual
parser = argparse.ArgumentParser()

# Usual arguments which are applicable for the whole script / top-level args
parser.add_argument('--verbose', help='Common top-level parameter',
                    action='store_true', required=False)

# Same subparsers as usual
subparsers = parser.add_subparsers(help='Desired action to perform', dest='action')

# Usual subparsers not using common options
parser_other = subparsers.add_parser("extra-action", help='Do something without db')

# Create parent subparser. Note `add_help=False` and creation via `argparse.`
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('-p', help='add db parameter', required=True)

# Subparsers based on parent

parser_create = subparsers.add_parser("create", parents=[parent_parser],
                                      help='Create something')
# Add some arguments exclusively for parser_create

parser_update = subparsers.add_parser("update", parents=[parent_parser],
                                      help='Update something')
# Add some arguments exclusively for parser_update

if __name__ == '__main__':
    arg = parser.parse_args()
    print(arg)
    print(arg.action) # action
    print(arg.p) # db

click Link to heading

click中也有类似的写法,不过是基于decorator改造的,也挺有意思的。SO问题。主要是我对于decorator也没什么研究,之后有时间了再进一步学习。