【问题标题】:Two argparse arguments that can be either positional or optional两个 argparse 参数,可以是位置的或可选的
【发布时间】:2018-08-10 00:50:27
【问题描述】:

我正在尝试用 Python 3.5(Debian 9 发布的 Python 版本)编写一个程序,它接受两个命令行参数:一个输入文件名和一个输出文件名。

  • 输入文件名必须在输出文件名之前或 本身前面加上-i
  • 输出文件名是可选的。如果存在,以及输入文件 名称前面没有-i,它必须在输入文件之后 名称或本身前面加上-o

因此我想接受以下命令行:

programname.py infilename
programname.py -i infilename
programname.py infilename outfilename
programname.py -i infilename outfilename
programname.py infilename -o outfilename
programname.py -i infilename -o outfilename
programname.py outfilename -i infilename
programname.py -o outfilename -i infilename
programname.py -o outfilename infilename

使用信息可能如下所示:

programname.py [-i] infilename [[-o] outfilename]

但我无法从documentation of the argparse module 中看出如何在add_argument() 的参数中表达这一点。当我为一个参数指定两个名称时,一个是位置的,一个是命名的,add_argument() 会引发异常:

ValueError: invalid option string 'infilename': must start with a character '-'

我在 Stack Overflow 上搜索过类似的问题,发现 hpaulj's answer to Python argparse - mandatory argument - either positional or optionalhpaulj's answer to argparse: let the same required argument be positional OR optional。这些答案中的构造使用一组两个互斥的参数,一个是位置的,一个是命名的。但它似乎不适用于多个这样的论点。尝试使用以这种方式构建的解析器解析 -i infilename outfilename 会产生不同的异常:

argparse.ArgumentError: argument INFILE: not allowed with argument -i

但是,argparse 本身无法打印此异常,甚至无法显示 --help

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  [8+ layers of method calls within `argparse.py` omitted]
  File "/usr/lib/python3.5/argparse.py", line 396, in _format_actions_usage
    start = actions.index(group._group_actions[0])
IndexError: list index out of range

已弃用的 optparse 模块将位置参数存储在一个单独的列表中,解析后运行的代码可以读取以填充 is None 的每个参数。 argparse 中此列表的直接对应项是 parser.add_argument('args', nargs=argparse.REMAINDER)。在调用parse_args() 之后手动处理位置参数是接受上面使用argparse 显示的所有命令行形式的唯一方法吗?

#!/usr/bin/env python3
import argparse
import traceback

def mkparser1():
    """Raise an error.

ValueError: invalid option string 'infilename': must start with a character '-'
"""
    parser = argparse.ArgumentParser()
    parser.add_argument("infilename", "-i", metavar="INFILE")
    parser.add_argument("outfilename", "-o", required=False, metavar="INFILE")
    return parser

def mkparser2():
    """Do not raise an error but return an inadequate parser.

When asked -i infilename outfilename
"""
    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("infilename", nargs="?", metavar="INFILE")
    group.add_argument('-i', dest="infilename", metavar="INFILE")
    group = parser.add_mutually_exclusive_group(required=False)
    parser.add_argument("outfilename", nargs="?", metavar="OUTFILE")
    parser.add_argument("-o", dest="outfilename", metavar="OUTFILE")
    return parser

def test():
    parser = mkparser2()
    argstrings = [
        "infilename",
        "-i infilename",
        "infilename outfilename",
        "-i infilename outfilename",
        "infilename -o outfilename",
        "-i infilename -o outfilename",
        "outfilename -i infilename",
        "-o outfilename -i infilename",
        "-o outfilename infilename",
        "--help",
    ]
    for s in argstrings:
        print("for", s)
        try:
            pargs = parser.parse_args(s.split())
        except Exception as e:
            traceback.print_exc()
        else:
            print("infilename is %s and outfilename is %s"
                  % (pargs.infilename, pargs.outfilename))

if __name__=='__main__':
    test()

【问题讨论】:

  • 也许更接近optparse 的方法是不定义任何位置,并使用parse_known_args。当您有两个带有“?”的位置时(或“*”或“+”),生活变得复杂。它仍然尝试按照它们来的顺序填充位置,而不知道可能已经设置了哪些选项。请记住,argparse 尝试以与顺序无关的方式解析选项。
  • 直到问了基本相同的问题后我才发现这个问题。你可以在这里找到我是如何解决它的:stackoverflow.com/a/49846807/5689064

标签: python argparse


【解决方案1】:

您也许可以让您的程序接受可变数量的位置参数(0 到 2 之间),这些参数将被添加到位置参数列表中(使用action="append"),并调用add_argument("-i",...)add_argument("-o",...) 来处理标志等价物。

通常,argparse 选项属于位置或可选类别(但不能同时属于两者)。因此,您需要将设置传递给允许一些冗余的 argparse,并在解析后处理冲突。例如,您可以将 argparse 配置为通过-i INPUT 和定位INPUT 接受输入文件,但是在解析后添加自定义检查以确保仅使用这两种形式中的一种。

伪代码:


parser.add_argument('infile', metavar="INFILE", nargs='?',
                    type=argparse.FileType('r'),
                    action='append', dest="positional_args")
parser.add_argument('outfile', metavar="OUTFILE", nargs='?', 
                    type=argparse.FileType('w'),
                    action='append', dest="positional_args")
parser.add_argument('-i', metavar="INFILE", dest="infile", default=None)
parser.add_argument('-o', metavar="OUTFILE", dest="outfile", default=None)

args = parser.parse_args([....])

# here insert check for conflicts between len(args.positional_args) and -i and -o
# example:
if sum([len(args.positional_args),
       args.infile is not None,
       args.outfile is not None]) != 2:
   parser.print_help()
   sys.exit(1)
...

infile = args.infile or args.positional_args.pop(0)
outfile = args.outfile or args.positional_args.pop(0)

【讨论】:

    猜你喜欢
    • 2011-05-27
    • 2019-04-13
    • 2014-12-29
    • 2018-04-29
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多