我遇到了一个简单的用例,
argparse
令人惊讶地似乎无法处理。除了拥有多个子解析器之外,我还希望有一个必需的位置参数。其基本原理是,该 CLI 应用程序可以通过简洁的语法轻松支持一种用例,并在子命令下嵌套更精细的选项。对于这篇文章,假设我正在编写一个 CLI 应用程序,用于使用以下语法标记文件:
# tag a file
argparse_test.py <file> [<tags>...]
# list tags
argparse_test.py tags <file>
使用示例:
$ argparse_test.py ./cat.jpg cute funny
$ argparse_test.py tags ./cat.jpg
> cute, funny
我的实现如下:
import argparse
# initialize main parser
parser = argparse.ArgumentParser()
parser.add_argument('file', help="The file to index.")
parser.add_argument('tags', nargs='*', help="Tags to append.")
# initialize `tags` subparser
subparsers = parser.add_subparsers()
tag_subparser = subparsers.add_parser("tags")
tag_subparser.add_argument('file', help="The file to list tags for.")
# 1. Failing test case
args = parser.parse_args(["./cat.jpg", "cute", "funny"])
print(args)
# 2. Failing test case (comment out #1 to reach this)
args = parser.parse_args(["tags", "./cat.jpg"])
print(args)
令人惊讶的是,这两种情况都失败了!
对于第一个测试用例:
usage: argparse_test.py [-h] file [tags ...] {tags} ...
argparse_test.py: error: argument {tags}: invalid choice: 'funny' (choose from 'tags')
对于第二个测试用例:
usage: argparse_test.py tags [-h] file
argparse_test.py tags: error: the following arguments are required: file
注释掉主解析器或子解析器将每次修复一个(但不是两者)测试用例。看起来这个问题源于
argparse
在处理所需的位置和子命令时的一些冲突。
有没有已知的方法可以通过 Python 3.8+ 中的
argparse
实现对此 CLI 语法的支持?
(顺便说一句,我已经在 Python 3.9、3.10 和 3.11 上对此进行了测试,所有这些都具有相同的行为)。
我有两种不同的解决方案。第一个将与您的示例一起使用。第二个是我推荐的。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("action")
options, remainder = parser.parse_known_args()
if options.action == "tags":
print(f"Show tags. Files: {remainder}")
else:
print(f"File: {options.action}")
print(f"Tags: {remainder}")
基本上,我会解析“动作”。
这种方法的问题是接口不一致:在一种情况下,第一个参数是文件名;在一种情况下,第一个参数是文件名。在另一种情况下,第一个参数是命令。
我提出了另一种解决方案,它引入了两个子命令:“add-tags”和“show-tags”。你可以随意称呼他们。
import argparse
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(dest="action")
add_tags = subparser.add_parser("add-tags")
add_tags.add_argument("file")
add_tags.add_argument("tags", nargs="+")
show_tags = subparser.add_parser("show-tags")
show_tags.add_argument("file")
print("\n# Add tags")
args = parser.parse_args(["add-tags", "./cat.jpg", "cute", "funny"])
print(args)
print("\n# Show tags")
args = parser.parse_args(["show-tags", "./cat.jpg"])
print(args)
输出:
# Add tags
Namespace(action='add-tags', file='./cat.jpg', tags=['cute', 'funny'])
# Show tags
Namespace(action='show-tags', file='./cat.jpg')
这种方法的优点是: