Docopt/Docopt-ng简单上手

DocOpt

docopt模块的思路和argparse不同,它认为,一个程序的命令行参数和选项应当通过文档自动实现,开发者的任务仅仅只是写出文档而已

注:DocOpt的思想是通用的,它有多种语言的解析器实现,包括Ruby、Cpp、Golang、Julia和一个用于Shell的二进制解析程序。

安装

现在的Docopt由Jazzband维护,名为docopt-ng,要进行安装,执行:

1
pip install docopt-ng

传统版本的docopt已经停止维护,但是在大部分发行版仓库中仍然包含:

1
2
3
4
5
# Debian
apt install python3-docopt

# RHEL
yum install python3-docopt

模块内容

文档

“文档”的定义是任意一串遵循BNF范式的Docstring,比如说,你可以自己定义:

1
2
3
docstr = '''
......
'''

你也可以直接写在__doc__范围内:

1
2
3
4
'''
......
'''
import ...

Usage:标识了开始解析的部分,文档中至少需要包含这一部分:

1
2
3
4
'''
Usage:
{}
'''.format(sys.argv[0])

程序名实际上可以任取,不过为了方便也可以直接使用sys.argv[0]

解析

使用docopt.docopt(Str[, argv=['参数']])函数进行解析并返回参数解析字典argv=表示默认传入的参数值列表。

解析字典中,针对不同类型的选项和参数的保存方式如下:

  • 单个参数:以参数名为键,值为字符串。
  • 单个无参选项:以选项名为键,值为布尔值。
  • 单个有参选项:以选项名为键,值为字符串。
  • 不定量参数:以参数名为键,值为字符串列表。
  • 不定量无参选项:以选项名为键,值为整数(选项出现次数)。
  • 不定量有参选项:以选项名为键,值为字符串列表。
命令行参数

使用< >包裹起来的部分,或完全是大写字母的部分,会被解析为命令行参数:

1
2
3
4
5
6
7
'''
Usage:
{} <pos_arg>
'''.format(sys.argv[0])

import docopt
args: dict = docopt.docopt(docstr)
1
2
3
4
5
6
7
'''
Usage:
{} POS_ARG
'''.format(sys.argv[0])

import docopt
args: dict = docopt.docopt(docstr)

然后,就可以通过args字典调用命令行参数,键名就是带有< >的命令行参数名:

1
args['<pos_arg>']

docopt-ng)也可以使用点引用方式调用命令行参数,不过此时,键名中的< >则需要省略:

1
args.pos_arg
不定量参数

有时候,重复的参数数量并不确定,这时候可以使用...来标识:

1
2
3
4
5
6
7
'''
Usage:
# 一个或任意多
{0} <pos_arg>...
# 零个或任意多
{0} [<pos_arg>...]
'''.format(sys.argv[0])

这样的参数,在字典中对应的值是一个列表。

选项

---开头的部分都会被解析为选项,短选项和长选项都支持:

1
2
3
4
'''
Usage:
{} -o <opt_arg>
'''.format(sys.argv[0])

需要注意的是,选项参数使用选项名作为字典的键进行调用,而不是使用选项参数名,字典的键内不会去掉选项名前面的---,例如:

1
args['-o']

docopt-ng)也可以使用点引用方式调用选项参数,不过此时,键名中的-则需要省略:

1
args.o

为了方便用户,你还应该把选项(及其长选项)拉出来单独写一段Options:文档:

1
2
3
4
5
6
7
'''
Usage:
{} -o <opt_arg>

Options:
-o, --option <opt_arg> 指定选项参数
'''.format(sys.argv[0])
  • 每行选项声明内容的开头都必须是-,选项部分不能重复。
  • 在原则上,短选项是唯一的。短选项之间是不能存在别名关系的。
  • 在选项和选项之间,使用,或者空格进行分隔都是可以的。
  • 选项参数可以使用< >包裹起来,或是完全使用大写字母。
  • 长选项和选项参数之间可以用空格隔开,也可以使用=连接。
  • 在选项和选项描述之间至少要有两个空格。
  • 如果给出了至少一个长选项,那么docopt会使用最后一个长选项名作为字典键保存选项参数。
  • 选项参数可以使用全大写的形式进行标识,也可以使用和之前所说的一致的<var>形式。

docopt实际上并不关心选项参数名(因为它不使用选项参数名保存参数值,而是直接使用选项名),因此用户在Options:段中给出的选项参数名并不必须要与Usage:段中一致:

1
2
3
4
5
6
Usage:
{} -o <arg>

Options:
-o, --option <opt_arg> 指定选项参数
'''.format(sys.argv[0])

但是,选项的类型(是否有参)必须在Usage:段和Options:段中保持一致,也就是说,两个地方必须要么都有选项参数名,要么都没有。

默认值

如果要设置选项的默认值,在Options:段的选项解释内容中添加[default: 值]

1
2
3
4
5
6
7
'''
Usage:
{} -o <opt_arg>

Options:
-o, --option <opt_arg> 指定选项参数 [default: 0]
'''.format(sys.argv[0])

注意,default:设置的默认值总是字符串类型。

在没有设置的情况下:

  • 定长参数(选项参数或命令行参数)的默认值是None
  • 不定长参数(选项参数或命令行参数)的默认值是[](空列表)。
  • 无参选项的默认值是False
  • 子命令的默认值是False
可选

大部分选项在使用时都应该是可选的,使用[]括起来就可以将选项解析为可选选项:

1
2
3
4
5
6
7
'''
Usage:
{} [-o <opt_arg>]

Options:
-o, --option <opt_arg> 指定选项参数
'''.format(sys.argv[0])

如果你的所有选项都是可选的,那么你可以干脆地使用[options]替代:

1
2
3
4
5
6
7
'''
Usage:
{} [options]

Options:
-o, --option <opt_arg> 指定选项参数
'''.format(sys.argv[0])

当然,如果只有部分选项不是可选的,也可以把它们单独列出:

1
2
3
4
5
6
7
'''
Usage:
{} -o <opt_arg> [options]

Options:
-o, --option <opt_arg> 指定选项参数
'''.format(sys.argv[0])

[options]会自动排除掉所有在Usage中已经出现的选项。

选项终结符

GNU风格的--选项终结符也可以被解析,只需要在选项和参数之间,或者子命令和参数之间添加[--]即可。这种方式的主要目的是,在某些特殊的情况下传递开头为-的命令行参数:

1
2
3
4
5
'''
Usage:
{} -o <opt_arg> [--] <pos_arg>
{} sub [--] [<pos_arg>...]
'''.format(sys.argv[0])

当然,就算你不写[--]docopt也是接受用户的--的,只是写出会更清晰。

标准输入符

有时候脚本接受来自标准输入的输入,此时只需要将[-]设为参数即可:

1
2
3
4
5
'''
Usage:
{} [-]
{} -o [-]
'''.format(sys.argv[0])
子命令

既没有被< >括起来或完全由大写字母组成,也不是以---开头的部分,会被解析为子命令:

1
2
3
4
'''
Usage:
{} sub
'''.format(sys.argv[0])

如果你觉得一行Usage不足以说明所有的子命令,那么也是可以分开写的:

1
2
3
4
5
'''
Usage:
{0} sub1
{0} sub2
'''.format(sys.argv[0])

如果在一个子命令的Usage中使用了[options],那么[options]会自动排除掉所有在Usage中已经出现了的选项,例如:

1
2
3
4
5
Usage:
# 不包含-t
{0} sub1 [options]
{0} sub2 -t
'''.format(sys.argv[0])

多个子命令可以分别编写Options:段:

1
2
3
4
5
sub1 Options:
...

sub2 Options:
...

但是,DocOpt并不会对不同的Options:段进行区分,这样做的效果和单独定义一个整体的Options:段没有区别,只是方便用户理解。

分组

有时候我们会希望两个参数或选项要么都出现,要么都不出现,这时候使用()将其结合成组就好了:

1
2
3
'''
Usage:
{} [(<arg1> <arg2>)]
互斥

有时候,多个选项或子命令之间的关系是互斥的,只需要有一个就好,对于这种关系,使用(A | B)就可以:

1
2
3
4
'''
Usage:
{} (start | stop)
'''.format(sys.argv[0])

当然,用[A | B]括起来,可以允许任何一个选项都不存在:

1
2
3
4
'''
Usage:
{} [--yes | --no]
'''.format(sys.argv[0])

这种情况在逻辑上不适用于接受参数的选项。

不定量选项

对于部分选项,我们可能会希望允许其多次出现。这可能是因为我们希望一个无参选项可以重复出现以提高强度,或是希望一个有参选项可以重复出现以多次追加选项参数,这同样可以通过分组和...不定量标识符的方式实现。

例如,对于计数型无参选项:

1
2
3
Usage:
{} [-c...]
'''.format(sys.argv[0])

对于允许重复出现的有参选项:

1
2
3
Usage:
{} (-c <opt_arg>)...
'''.format(sys.argv[0])

你也可以允许它一次都不出现:

1
2
3
Usage:
{} [(-c <opt_arg>)...]
'''.format(sys.argv[0])
  • 注意,这里使用分组的方式将选项与其选项参数放进一个组内,如果不这么做,那么仅仅意味着选项参数是不定量的。
其他

docopt.docopt()函数在解析文档时,默认会自动生成-h--help--version(可选)选项的行为。这一行为可以通过一些参数进行修改。

docopt.docopt()函数接受的其他参数:

  • argv: list[str] | str:解析的命令行部分,默认为None
  • help: booldefault_help: booldocopt-ng):是否自动生成-h--help选项的帮助文档。
  • version: str:版本号,可以用于自动生成在Options:段内定义的--version选项的行为。
  • options_first: bool:只允许选项位于参数之前。即传统命令行选项的行为。