如何在 argparse 中为现有参数添加位置选项

How to add positional options for existing arguments in argparse

我正在处理一个 (Python 3.x) 脚本(由其他人编写),其中输入和输出当前使用标记的可选参数指定,如下所示:

parser.add_argument('-i', '--input', nargs='?', type = argparse.FileType('r'),
                    default=sys.stdin, dest='inputfile')
parser.add_argument('-o', '--output-file', nargs='?', type=argparse.FileType('w'),
                    default=sys.stdout, dest='outputfile')

我想升级此脚本,以便可以将输入和输出文件指定为位置参数,同时保持现有标志参数以实现向后兼容性。我还想智能地处理可能因将标记参数与位置参数混合而产生的潜在冲突(即,如果仅给出 -i-o 之一,则会自动传递单个位置参数另一个和两个位置参数会引发冗余错误,而如果同时给出 -i-o,则任何位置参数都会引发冗余错误。

注意:当前编写的脚本不接受任何位置参数,但它接受其他标志,除了与输入和输出文件相关的参数之外,有些有参数有些没有。

argparse 是否可行(如果可行,如何实现)或者我是否必须使用其他方法重写参数解析(如果可行,您有什么建议)?

坚持使用 FileType 会很尴尬。 type 打开或创建文件。因此,当您只需要 2 个文件时,您可能会打开 4 个文件。但是如果其中一个文件是 stdinout,您不想关闭它。而且您无法处理 positional ,它可以根据给定的其他参数进行读取或写入。

您可以尝试定义 4 个默认字符串参数,其中 2 个已标记,2 个 nargs='?' 位置。给他们不同的dest。然后,您可以将您的智慧应用于 4 个可能的值。默认的 default None 应该足够清楚地表明没有提供值。一旦你决定了这两个文件名,你就可以打开并使用它们。较新的 Python 建议使用 with 上下文,尽管当文件已经打开时这可能会很尴尬(例如 sys.stdin)。

我认为您不应该尝试在 argparse 中实现该逻辑。解析后做。

对于那些对更明确的答案感兴趣的人,以下是我最终如何实施@hpaulj 的建议:

首先我为输入和输出文件参数定义了一个参数组:

files = parser.add_argument_group('file arguments:',description='These arguments can also be provided as postional arguments, in which case the input file comes first.')
files.add_argument('-i', '--input', nargs='?',
                    help='Source of the words to be syllabified.  If None or -, then input will be read from stdin.',
                    dest='inputfile')
files.add_argument('-o', '--output-file', nargs='?',
                    help='Destination of the syllabified words.  If None or -, then ouput will be written to stdout.',
                    dest='outputfile')
files.add_argument('fileone',nargs='?',
                     help=argparse.SUPPRESS)
files.add_argument('filetwo',nargs='?',
                     help=argparse.SUPPRESS)

这让我可以将参数放在一起,与程序的其他参数分开,并且可以更好地控制帮助文本的显示方式,使其最有意义。

然后,在解析参数 (args = parser.parse_args()) 之后,我添加了以下逻辑来确定正确的输入和输出是什么,并根据需要打开文件或标准输入和标准输出:

if (args.inputfile == None or args.inputfile == '-'):
    if (args.outputfile == None or args.outputfile == '-'):
        if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
            input = sys.stdin
            output = sys.stdout
        elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
            try:
                input = open(args.fileone,'r')
                output = sys.stdout
            except:
                input = sys.stdin
                output = open(args.fileone,'w')
        else:
            input = open(args.fileone,'r')
            output = open(args.filetwo,'w')
    else:
        if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
            input = sys.stdin
            output = open(args.outputfile,'w')
        elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
            input = open(args.fileone,'r')
            output = open(args.outputfile,'w')
        else:
            print("Error: too many files")
            print("Both -o and positional output file given")
            sys.exit(1)
else:
    if (args.outputfile == None or args.outputfile == '-'):
        if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
            input = open(args.inputfile,'r')
            output = sys.stdout
        elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
            input = open(args.inputfile,'r')
            output = open(args.fileone,'w')
        else:
            print("Error: too many files")
            print("Both -i and positional input file give")
            sys.exit(1)
    else:
        if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
            input = open(args.inputfile,'r')
            output = open(args.outputfile,'w')
        elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
            print("Error: too many files")
            print("Both -i and -o given with a positional file")
            sys.exit(1)
        else:
            print("Error: too many files")
            print("Both -i and -o given with positional files")
            sys.exit(1)

如您所见,我决定接受默认的 None- 作为引用 stdin/stdout 的可能性。这复制了 FileType 参数的行为,该参数也以这种方式接受 -

剩下的一个歧义是 "None"(即单词 "None" 的字符串)与 None(即 NoneType class)不同并且帮助消息可能被解释为暗示 -i None 应该引用标准输入。但是,我认为这里的区别对于大多数 python 用户来说应该是显而易见的,因此我不会进一步使逻辑复杂化来解释这种可能性。