argparse:如何一起使用所需的位置和子解析器?

问题描述 投票:0回答:1

我遇到了一个简单的用例,

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 上对此进行了测试,所有这些都具有相同的行为)。

python argparse
1个回答
0
投票

我有两种不同的解决方案。第一个将与您的示例一起使用。第二个是我推荐的。

第一个解决方案

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

这种方法的优点是:

  • 一致的接口:第一个参数始终是命令
  • 可扩展性:我们稍后可以添加更多子命令,例如“remove-tags”,“clear-all-tags”,...
© www.soinside.com 2019 - 2024. All rights reserved.