如何处理与 argparse 相关的命令行参数?

How to handle command line args that have relations with argparse?

假设我有一个名为 myprog 的程序,它将一些文件名作为输入,我还想使用命令行参数为每个文件设置打开模式。 例如

myprog --input a.txt --mode r --input b.txt --input c.txt --mode a

这意味着使用模式 r 打开文件 a.txt,文件 b.txt 没有 --mode arg,因此使用默认模式 r 打开它,对于文件 c.txt,请使用 a 模式打开它。

这是一个棘手的问题,因为 argparse 无法让您知道特定 --mode 与哪个 --input 关联。您可以更改命令的结构,以便文件名和模式由标记字符分隔:

myprog --input a.txt:r --input b.txt --input c.txt:a

显然,这假定您没有名称以 :<mode> 结尾的文件,其中 <mode> 是任何可接受的文件模式。如果这是一个 OK 结构,那么这就像编写自定义操作或类型来解析字符串和 return 一个合适的对象一样简单。例如

def parse_fstr(s):
    filename, _, mode = s.rpartition(':')
    return (filename, mode or 'r')

其他解决方案可能涉及使用 nargs='*' 然后解析传递的参数列表。


最后,要轻松实现您实际的要求,我们需要做一个假设。假设 argparse 将从左到右解析项目。考虑到库的功能,据我所知,这是唯一合理的实现选择...

鉴于该实现,我们可以使用自定义类型和自定义 Action 来做到这一点。该类型只是一个将 filenamemode 组合在一起的结构。每次我们点击 --input 并将其附加到列表时,我们将使用 argparse 来构造此类型的新实例(argparse 开箱即用)。接下来,我们将编写一个自定义操作,以在每次遇到 --mode 参数时更新列表中最后一个 "file struct" 的 mode

import argparse


class FileInfo(object):
    def __init__(self, name, mode='r'):
        self.name = name
        self.mode = mode

    def __repr__(self):
        return 'FileInfo(name={!r}, mode={!r})'.format(self.name, self.mode)


class UpdateMode(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        try:
            last_file_info = namespace.input[-1]
        except IndexError:
            # No file-info added yet.  Error.
            parser.error('{} must come after an --input'.format(option_string or '--mode'))

        last_file_info.mode = values


parser = argparse.ArgumentParser()
parser.add_argument('--input', action='append', type=FileInfo)
parser.add_argument('--mode', action=UpdateMode)
print(parser.parse_args())

如果 --mode 出现在任何 --input 之前,我选择抛出错误,但如果 2 --mode 跟在 --input 之后,我只是覆盖以前的价值。如果您想进行更多的错误检查,只需在 FileInfo class 中编写更多代码以确保在您更新模式时没有设置任何模式是一件简单的事情.

如果命令行是这样的:

myprog --input a.txt --mode r --input c.txt --mode a --input b.txt

添加一些这样的代码就可以了:

import argparse

parser = argparser.ArgumentParser()
parser.add_argument('--input', action='append')
parser.add_argument('--mode', action='append')
args = parser.parse_args()
args_dict = vars(args)

然后就可以解析args对象,args_dict变量。值是这样的:

$ python test.py --input test.txt --mode w --input test3.txt --input test2.txt --mode a
{'mode': ['w', 'a'], 'input': ['test.txt', 'test3.txt', 'test2.txt']}

您可以在 args_dict变量,输入列表的剩余部分(这里是'test2.txt'),可以用'r'模式打开。

但是如果你的命令行必须这样写:

myprog --input a.txt --mode r --input b.txt --input c.txt --mode a

我认为用 'r' 模式解析 b.txt 并不容易,因为 argparse 不知道哪个模式绑定到相对输入...


从@mgilson 的评论和回答中获得灵感,我找到了另一种定义 Action 子类的方法,并使 'mode' 输入有用。

class ExtendReadOnlyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        inputs = namespace.input
        modes = getattr(namespace, self.dest)
        if modes is None:
            modes = []
        modes.extend(['r' for i in range(len(inputs) - len(modes))])
        modes[-1] = values
        setattr(namespace, self.dest, modes)

客户端代码可以是这样的:

import argparse

parser = argparser.ArgumentParser()
parser.add_argument('--input', action='append')
parser.add_argument('--mode', action=ExtendReadOnlyAction)
args = parser.parse_args()
args_dict = vars(args)

然后我们就可以解析args对象,args_dict变量就更简单了。如果命令行是这样的:

$ python test.py --input test.txt --mode w --input test2.txt --input test3.txt --mode a

结果将是:

{'mode': ['w', 'r', 'a'], 'input': ['test.txt', 'test2.txt', 'test3.txt']}

另一种特殊方式,如果命令行是这样的:

$ python test.py --input test.txt --mode w --input test2.txt --input test3.txt --input test4.txt

结果将是:

{'input': ['test.txt', 'test2.txt', 'test3.txt', 'test4.txt'], 'mode': ['w']}

然后你可以更容易地解析字典,输入参数中的 'test2.txt ~ test4.txt' 将具有默认的 'r' 模式:)