如何使用 argparse 处理 CLI 子命令
Posted
技术标签:
【中文标题】如何使用 argparse 处理 CLI 子命令【英文标题】:How to handle CLI subcommands with argparse 【发布时间】:2019-02-08 19:18:46 【问题描述】:我需要实现程序接受子命令的命令行界面。
例如,如果程序名为“foo”,则 CLI 如下所示
foo cmd1 <cmd1-options>
foo cmd2
foo cmd3 <cmd3-options>
cmd1
和 cmd3
必须与它们的至少一个选项一起使用,并且三个 cmd*
参数始终是互斥的。
我正在尝试在 argparse 中使用子解析器,但目前没有成功。问题在于cmd2
,它没有参数:
如果我尝试添加不带参数的子解析器条目,parse_args
返回的命名空间将不包含任何信息告诉我选择了此选项(请参见下面的示例)。
如果我尝试将 cmd2
作为参数添加到 parser
(而不是子解析器),那么 argparse 将期望 cmd2
参数后跟任何子解析器参数。
有没有一种简单的方法可以通过argparse
实现这一目标?用例应该很常见……
以下是我迄今为止所尝试的更接近我需要的内容:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_2 = subparsers.add_parser(cmd2, help='...')
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
args = parser.parse_args()
【问题讨论】:
【参考方案1】:首先,子解析器从不插入到命名空间中。在您发布的示例中,如果您尝试将脚本运行为:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1')
test_args.py
包含您提供的代码(import argparse
开头,print(args)
结尾)。
请注意,没有提及 cmd1
仅提及它的论点。 这是设计使然。
正如 cmets 中所指出的,您可以将传递 dest
参数的信息添加到 add_subparsers
调用。
处理这些情况的通常方法是使用子解析器的set_defaults
方法:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_1.set_defaults(parser1=True)
parser_2 = subparsers.add_parser('cmd2', help='...')
parser_2.set_defaults(parser2=True)
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
parser_3.set_defaults(parser_3=True)
args = parser.parse_args()
print(args)
结果:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1', parser1=True)
$python3 test_args.py cmd2
Namespace(parser2=True)
通常,不同的子解析器在大多数情况下会以完全不同的方式处理参数。通常的模式是使用不同的函数来运行不同的命令并使用set_defaults
设置func
属性。当您解析参数时,您只需调用该可调用对象:
subparsers = parser.add_subparsers()
parser_1 = subparsers.add_parser(...)
parser_1.set_defaults(func=do_command_one)
parser_k = subparsers.add_parser(...)
parser_k.set_defaults(func=do_command_k)
args = parser.parse_args()
if args.func:
args.func(args)
【讨论】:
我对“从未插入”有异议。只需给“add_subparsers”一个dest
。
@hpaulj 你是对的。但是,我个人从未使用过它。正如我所说,为不同的子解析器提供不同的处理功能需要更多的命令,因此使用set_defaults
是一个更好的解决方案。仅适用于您希望使用dest
参数的最简单的情况。【参考方案2】:
如果给add_subparsers
命令提供dest
,则可以将子解析器标识添加到主Namespace
。
来自文档:
但是,如果需要检查被调用的子解析器的名称,add_subparsers() 调用的 dest 关键字参数将起作用:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(dest='subparser_name')
>>> subparser1 = subparsers.add_parser('1')
>>> subparser1.add_argument('-x')
>>> subparser2 = subparsers.add_parser('2')
>>> subparser2.add_argument('y')
>>> parser.parse_args(['2', 'frobble'])
Namespace(subparser_name='2', y='frobble')
默认情况下,dest
是 argparse.SUPPRESS
,这会阻止 subparsers
将名称添加到 namespace
。
【讨论】:
以上是关于如何使用 argparse 处理 CLI 子命令的主要内容,如果未能解决你的问题,请参考以下文章