Python Argparse 条件需要的参数

Posted

技术标签:

【中文标题】Python Argparse 条件需要的参数【英文标题】:Python Argparse conditionally required arguments 【发布时间】:2014-10-26 21:06:04 【问题描述】:

我已经进行了尽可能多的研究,但我还没有找到仅在某些条件下才需要某些命令行参数的最佳方法,在这种情况下,只有在给出其他参数的情况下。以下是我想要在非常基本的层面上做的事情:

p = argparse.ArgumentParser(description='...')
p.add_argument('--argument', required=False)
p.add_argument('-a', required=False) # only required if --argument is given
p.add_argument('-b', required=False) # only required if --argument is given

据我所见,其他人似乎只是在最后添加了自己的支票:

if args.argument and (args.a is None or args.b is None):
    # raise argparse error here

有没有办法在 argparse 包中本地执行此操作?

【问题讨论】:

你看过argparse子解析器吗?它们将允许您执行 $ git commit <commit args only>$ git merge <merge args only> 之类的操作。 乔尔,感谢您的评论。我已经看到了 argparse 的子解析器方面,但我希望在没有位置参数的情况下做到这一点。如果这是唯一的方法,虽然这没什么大不了的 --a--b 可以单独给出吗? 【参考方案1】:

一段时间以来,我一直在寻找此类问题的简单答案。您需要做的就是检查'--argument' 是否在sys.argv 中,所以基本上对于您的代码示例,您可以这样做:

import argparse
import sys

if __name__ == '__main__':
    p = argparse.ArgumentParser(description='...')
    p.add_argument('--argument', required=False)
    p.add_argument('-a', required='--argument' in sys.argv) #only required if --argument is given
    p.add_argument('-b', required='--argument' in sys.argv) #only required if --argument is given
    args = p.parse_args()

这种方式required 接收TrueFalse 取决于用户是否使用--argument。已经对其进行了测试,似乎可以正常工作并保证-a-b 在彼此之间具有独立的行为。

【讨论】:

简单、简洁、令人难以置信的作品! 如果给出-a--argument 不存在,有没有办法让它抛出错误? @VivekSubramanian 你可以在args = p.parse_args()之后这样做 这将与位置参数不兼容,因为它会忽略选项结束标记 --。例如。 sys.argv[1:] = ['--', '--argument', 'foobar'] -> -a-b 不应该是必需的,但它们是。 这也不会正确显示在 -h/--help 消息中,因为它当时没有足够的信息!所以可能会让用户有点困惑【参考方案2】:

您可以通过为--argument 提供自定义操作来实施检查,该操作将采用额外的关键字参数来指定在使用--argument 时应该需要哪些其他操作。

import argparse

class CondAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        x = kwargs.pop('to_be_required', [])
        super(CondAction, self).__init__(option_strings, dest, **kwargs)
        self.make_required = x

    def __call__(self, parser, namespace, values, option_string=None):
        for x in self.make_required:
            x.required = True
        try:
            return super(CondAction, self).__call__(parser, namespace, values, option_string)
        except NotImplementedError:
            pass

p = argparse.ArgumentParser()
x = p.add_argument("--a")
p.add_argument("--argument", action=CondAction, to_be_required=[x])

CondAction 的确切定义取决于--argument 应该做什么。但是,例如,如果 --argument 是常规的、采取一个参数并保存它的操作类型,那么仅从 argparse._StoreAction 继承就足够了。

在示例解析器中,我们在--argument 选项中保存了对--a 选项的引用,当在命令行上看到--argument 时,它会将--a 上的required 标志设置为@ 987654333@。处理完所有选项后,argparse 会验证是否已设置任何标记为必需的选项。

【讨论】:

正如所写它不起作用,因为Action.__call__ 返回not implement 错误。但是调整xrequired 属性的基本想法应该可行。 好点。如果继承自argparse.Action,则无需调用(未实现的)父类__call__。如果您从其他Action 子类之一继承,则应该这样做。 (作为妥协,我编辑了答案以保留超类调用,但捕获并忽略NotImplementedError。)【参考方案3】:

您的后解析测试很好,特别是如果使用 is None 测试默认值符合您的需求。

http://bugs.python.org/issue11588 'Add "necessarily inclusive" groups to argparse' 研究使用groups 机制(mutuall_exclusive_groups 的泛化)实现这样的测试。

我编写了一组 UsageGroups 来实现像 xor(互斥)、andornot 这样的测试。我认为那些很全面,但我无法用这些操作来表达你的情况。 (看起来我需要nand - 不是和,见下文)

此脚本使用自定义Test 类,它实质上实现了您的解析后测试。 seen_actions 是解析器看到的操作列表。

class Test(argparse.UsageGroup):
    def _add_test(self):
        self.usage = '(if --argument then -a and -b are required)'
        def testfn(parser, seen_actions, *vargs, **kwargs):
            "custom error"
            actions = self._group_actions
            if actions[0] in seen_actions:
                if actions[1] not in seen_actions or actions[2] not in seen_actions:
                    msg = '%s - 2nd and 3rd required with 1st'
                    self.raise_error(parser, msg)
            return True
        self.testfn = testfn
        self.dest = 'Test'
p = argparse.ArgumentParser(formatter_class=argparse.UsageGroupHelpFormatter)
g1 = p.add_usage_group(kind=Test)
g1.add_argument('--argument')
g1.add_argument('-a')
g1.add_argument('-b')
print(p.parse_args())

示例输出为:

1646:~/mypy/argdev/usage_groups$ python3 issue25626109.py --arg=1 -a1
usage: issue25626109.py [-h] [--argument ARGUMENT] [-a A] [-b B]
                        (if --argument then -a and -b are required)
issue25626109.py: error: group Test: argument, a, b - 2nd and 3rd required with 1st

usage 和错误消息仍然需要处理。而且它没有做任何解析后测试做不到的事情。


如果(argument & (!a or !b)),您的测试会引发错误。相反,允许的是!(argument & (!a or !b)) = !(argument & !(a and b))。通过将nand 测试添加到我的UsageGroup 类中,我可以将您的案例实现为:

p = argparse.ArgumentParser(formatter_class=argparse.UsageGroupHelpFormatter)
g1 = p.add_usage_group(kind='nand', dest='nand1')
arg = g1.add_argument('--arg', metavar='C')
g11 = g1.add_usage_group(kind='nand', dest='nand2')
g11.add_argument('-a')
g11.add_argument('-b')

用法是(使用!()标记'nand'测试):

usage: issue25626109.py [-h] !(--arg C & !(-a A & -b B))

我认为这是使用通用使用组来表达这个问题的最短、最清晰的方式。


在我的测试中,成功解析的输入是:

''
'-a1'
'-a1 -b2'
'--arg=3 -a1 -b2'

应该引发错误的是:

'--arg=3'
'--arg=3 -a1'
'--arg=3 -b2'

【讨论】:

【参考方案4】:

对于论点,我想出了一个这样的快速n-dirty 解决方案。 假设:(1) '--help' 应该显示帮助而不是抱怨所需的参数,(2) 我们正在解析 sys.argv

p = argparse.ArgumentParser(...)
p.add_argument('-required', ..., required = '--help' not in sys.argv )

这可以轻松修改以匹配特定设置。 对于所需的位置(如果在命令行上给出例如'--help',这将变得不需要)我想出了以下内容:[位置不允许required=...关键字arg!]

p.add_argument('pattern', ..., narg = '+' if '--help' not in sys.argv else '*' )

如果指定了“--help”,基本上这会将命令行上所需的“模式”出现次数从一个或多个变为零个或多个。

【讨论】:

我不介意一两次反对,但我想知道为什么,特别是因为另一个答案 ***.com/a/44210638/26083(此时赞成 5)基本上是一样的【参考方案5】:

在解决http://bugs.python.org/issue11588 之前,我只会使用nargs

p = argparse.ArgumentParser(description='...')
p.add_argument('--arguments', required=False, nargs=2, metavar=('A', 'B'))

这样,如果有人提供--arguments,它将有2个值。

也许它的 CLI 结果可读性较差,但代码要小得多。你可以用好的文档/帮助来解决这个问题。

【讨论】:

【参考方案6】:

这实际上与@Mira 的答案相同,但我想在给出选项时需要额外 arg 的情况下展示它:

例如,如果给定了--option foo,那么如果给定--option bar,则还需要一些不需要的参数:

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--option', required=True,
        help='foo and bar need different args')

    if 'foo' in sys.argv:
        parser.add_argument('--foo_opt1', required=True,
           help='--option foo requires "--foo_opt1"')
        parser.add_argument('--foo_opt2', required=True,
           help='--option foo requires "--foo_opt2"')
        ...

    if 'bar' in sys.argv:
        parser.add_argument('--bar_opt', required=True,
           help='--option bar requires "--bar_opt"')
        ...

它并不完美 - 例如 proggy --option foo --foo_opt1 bar 是模棱两可的,但我需要做的事情。

【讨论】:

【参考方案7】:

我找到了一个简单而干净的解决方案:

不会因使用 in sys.argv 测试的过度简化解析而导致歧义和功能损失。 无需实现特殊的argparse.Actionargparse.UsageGroup 类。 即使对于多个复杂的决定性参数也能简单使用。

我只注意到一个缺点(有些人可能认为这是可取的):帮助文本会根据决定性参数的存在而变化。

我们的想法是使用argparse 来解析决定性的参数,而不是过于简单地使用in sys.argv 测试。然后在主解析器中重用第一个解析器的定义。

import argparse

# First parse the deciding arguments.
deciding_args_parser = argparse.ArgumentParser(add_help=False)
deciding_args_parser.add_argument(
        '--argument', required=False, action='store_true')
deciding_args, _ = deciding_args_parser.parse_known_args()

# Create the main parser with the knowledge of the deciding arguments.
parser = argparse.ArgumentParser(
        description='...', parents=[deciding_args_parser])
parser.add_argument('-a', required=deciding_args.argument)
parser.add_argument('-b', required=deciding_args.argument)
arguments = parser.parse_args()

print(arguments)

【讨论】:

以上是关于Python Argparse 条件需要的参数的主要内容,如果未能解决你的问题,请参考以下文章

python之定义参数模块argparse的基本使用

Python Argparse“需要以下参数”错误

需要将`-`字符嵌入到python argparse中的参数中

Python学习之Argparse 解析脚本参数详解

Python argparse 可选 arg 需要其他参数

使用 argparse 将参数发送到 Python 脚本中的函数