argparse与click

前言

同样作为处理python命令行参数的库,
有以下几种

  1. getopt(简单)
  2. optparse(较为完善)
  3. argparse(更完善但还是不好用)
  4. click
  5. docopt

只记得argparse比optparse更完整一些,其他忘了.
如今又出现了一个click.
这里从使用讲起并对比一下.

argparse

基础使用

1
2
3
4
5
6
7
8
9
10
import argparse

# 初始化
parser = argparse.ArgumentParser()
# 添加参数
parser.add_argument(dest="m", help="enter the m...", type=int)
# 解析参数
args = parser.parse_args()
# 调用时使用dest后的值
print(args.m)

必选参数与格式化

像是普通函数用的形参,即为必选参数
帮助会显示为python test.py m n

1
2
parser.add_argument(dest="m", help="enter the m...", type=int)
parser.add_argument(dest="n", help="enter the n...", type=int)

可选参数与默认值

看起来像别人写的那种参数,即为可选参数.
用参数的方法为python test.py -a <a> -b <b>.
或b与值之前可以没有空格

1
2
3
4
parser.add_argument("-a", "--a_number", dest="a", help="enter the a...", type=int, default=2)
parser.add_argument("-b", "--b_string", dest="b", help="enter the b...")
# 有时也可以指定这种为必选
parser.add_argument("-o", "--output", dest="output", help="output file name", required=True)

TODO 接收列表的参数

nargs?

可多次使用的参数

像sed的-e参数一样可以多次使用的参数.
使用action中的append可以将该参数对应的值放在一个列表中

1
parser.add_argument("--file", dest="files", action="append", type=argparse.FileType('rb'))

出现即具有意义的flag

完全不需要指定值.
通常是默认不启用,使用该flag后则表示启用.
比如 --gpu 默认不使用,使用后表示使用GPU.
也有一些默认启用的flag使用这用方式,比如类似emacs的那种 --gui.
想表达不启用的意图时,可以使用相反意义的flag比如 --no-gui.
程序中通过action的赋值来定义默认行为,
store~true表示一旦出现就作为true保存~,
store~false相反~.

1
parser.add_argument("--gui", dest="is_gui", action="store_true")

可人为定值的flag

通常的使用场景是默认启用,可以人为指定不使用.
比如 --use-gpu=True--use-gpu=False.
但默认启用的情况下,
想表达否定意义时到底是使用 --use-gpu=False 还是使用 --no-gpu,
就看个人喜好了.

1
parser.add_argument("--use-gpu", dest="use_gpu", default=True)

选择题

或者叫备选项.
从几个选项中选取可以采取的值.
argparse中使用choices定义可选的值.

1
parser.add_argument("--method", choices=["linear", "square", "cubic"], default="linear")

TODO 互斥参数和组合参数

比如画一个矩形,
如果使用两点式则必须要有两点,
使用中心的宽高的方式则必须要有中心和宽高.
同时不能混用

1
2
3
4
5
6
7
# 互斥参数
# 帮助会显示为 test.py [-h] [--fangda | --suoxiao]
group = parser.add_mutually_exclusive_group()
group.add_argument("--fangda", action="store_true")
group.add_argument("--suoxiao", action="store_true")

# TODO 组合参数

子解析器

类似git后加的一堆子命令,
都可以通过建立argparse的子解析器来实现.
add~subparsers建立一个子解析器的容器~.
该容器只能有一个但容器内可以装互斥的多个子命令.
子命令的使用方法同一般命令.
加入的参数则成为该子命令特有的参数.
子解释器还可以有孙解释器.
子又有孙,孙又有子…

1
2
3
4
5
6
subparsers = parser.add_subparsers()
parser_status = subparsers.add_parser('status',help='run a status subcommand')
parser_status.add_argument("--name",action="store", help="argument of status subcommand", required=True)

parser_merge = subparsers.add_parser('merge',help='run a merge subcommand')
parser_merge.add_argument(...)

但为了方便调用不同的子命令,同时又不用多写if和else
可以使用以下技巧
set~defaults本来是用来声明默认值的~,
除了声明常量或变量,
还可以用来给子解释器声明一个成员函数.
则该函数即可与子解释器使用通用的方式绑定.
省去if和else,
和设计模式有点像.

1
2
3
4
5
6
7
def status(args):
print(args.name * args.y)

parser_status.set_defaults(func=status)

# 使用
args.func(args)

click[以及与argparse的对比]

整体对比

  1. 优点

    • click省去了dest参数,不用给参数取两个名字(调用时和内部处理时)
    • click省去了args对象以及使用args.x调用的过程
    • click使用自定义数据类型,并传入type的方式减少了choices这个参数
    • click使用修饰器的方式直接指定命令与同名函数绑定,可以不需要先定义函数再声明作为args对象的参数,简化了流程
    • click支持链式使用子命令(可能是由于linux的管道命令必须借助stdout吧)
    • click好像对编译跨平台的可执行二进制文件有帮助
  2. 缺点

    • 貌似不支持组合参数和一些非互补意义的组合参数

基础使用

使用 @click.command 来声明下方的函数是一个命令,并与同名命令绑定
使用 @click.option()@click.argument 来添加参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import click

@click.command()
@click.argument('numbers', nargs=-1, type=int)
@click.option('--summing', is_flag=True,
help='sum the integers (default: find the max)')
def main(numbers, summing):
"""Process some integers."""
if summing == True:
print(sum(numbers))
else:
print(max(numbers))

if __name__ == "__main__":
main()

必选参数与格式化

这里还使用nargs指定了参数的个数是不限制

1
@click.argument('numbers', nargs=-1, type=int)

可选参数和默认值

与argparse没有太大区别,带破折号即可

1
@click.option("--hello", default="world", help="say hello")

flag参数的使用

click里保留了值不可编辑的flag,
将具有同样效果(比如实现默认关闭,使用参数打开)但有不同做法的功能砍掉,
缩小实现效果的方法空间,代码易于理解,非常python风格.

因此要么是默认关闭,使用flag表示打开.
要么使用互斥又互补的两个相反意义的flag,可以用于表示默认开启.

但这也是click中仅存的互斥参数的用法了.

1
@click.option("--flag", is_flag=True)

局限的互斥参数

使用斜线分隔时默认表示互斥关系,并且值只能是一个True,一个False

1
2
3
4
@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo('Debug mode is %s' % ('on' if debug else 'off'))

TODO 互斥参数

备选项

click通过封装数组到自定义的类型,将choice统一到type参数中,
比argparse的API简化了参数表

1
@click.option('--language', type=click.Choice(['c', 'c++']))

子命令

click用command概念来修饰一个绑定在特定参数下的命令,
值得一提的是,
函数名直接作为了自命令的名字,不用再重新取名.
而command可以附属于group概念,
group本身可以绑定一个函数,
又可以通过add~command来添加子命令~.

另外,click还支持使用多个子命令来形成链式的子命令使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@click.group()
def cli():
pass

@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)

补充用的quick

可以给命令加一个gui方便参数的输入

  1. 写死要使用gui

    核心就是一个quick.gui~it~()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import quick
    import click

    @click.command()
    @click.option("--hello", default="world", help="say hello")
    @click.option("--password", help="input a password",\
    hide_input=True, prompt=True)
    @click.option("--minus", type=float, help="input two numbers", nargs=2)
    @click.option("--flag", is_flag=True)
    @click.option('--shout/--no-shout', default=True)
    @click.option('--language', type=click.Choice(['c', 'c++']))
    @click.option('-v', '--verbose', count=True)
    def example_cmd(**argvs):
    for k, v in argvs.items():
    print(k, v, type(v))


    if __name__ == "__main__":
    quick.gui_it(example_cmd)
  2. 可选使用gui

    核心是 quick.gui_option
    使用时使用–gui来指定使用GUI

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import quick
    import click

    @quick.gui_option
    @click.command()
    @click.option("--hello", default="world", help="say hello")
    @click.option('--shout/--no-shout', default=True)
    @click.option('--language', type=click.Choice(['c', 'c++']))
    @click.option('-v', '--verbose', count=True)
    def example_cmd(**argvs):
    for k, v in argvs.items():
    print(k, v, type(v))

    if __name__ == "__main__":
    example_cmd()

docopt

参考

  1. argparse的使用
  2. argparse的详细使用(入门教程还是掘金好)
  3. argparse子命令详细
  4. argparse官网的最原汁原味(函数的解释是原始的,才能明白高级的用法是利用它的某个方面)
  5. 知乎上的对比
  6. click的入门
  7. 想看的
  8. 互斥参数的一种实现方法