2014-09-02 108 views
19

尽管我已经做了尽可能多的研究,但是我还没有找到最好的方法来使某些cmdline参数仅在某些条件下是必需的,在这种情况下,只有在给出其他参数的情况下。这是我想在一个非常基本的水平做:Python Argparse有条件需要的参数

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包内本地执行此操作?

+1

你看过'argparse'子分析器吗?他们将允许你做'git commit '或'$ git merge '。 – 2014-09-02 14:43:59

+0

乔尔,感谢您的评论。我已经看到了argparse的subparser方面,但我希望能够在没有位置参数的情况下做到这一点。如果这是唯一的方法,但它不是什么大问题 – DJMcCarthy12 2014-09-02 14:49:43

+0

可以单独给出'--a'和'--b'吗? – hpaulj 2014-09-02 19:41:59

回答

9

您可以通过提供--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看出在命令行上,它设置于所述--arequired标志True。一旦处理完所有选项,​​将验证是否已设置标记为必需的任何选项。

+1

由于写入它不起作用,因为“Action .__ call__'返回一个”not implement“错误。但是调整'x'的'required'属性的基本思想应该可行。 – hpaulj 2014-09-02 19:35:06

+0

好点。如果你从'argparse.Action'继承,那么就不需要调用(未实现)父类'__call__'。如果您从另一个“Action”子类继承,则应该这样做。 (作为一种妥协,我编辑了答案以保留超类的调用,但赶上并忽略'NotImplementedError'。) – chepner 2014-09-02 19:38:36

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

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测试我UsageGroup类,我可以为实现你的情况

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

直到http://bugs.python.org/issue11588是解决了,我只是用nargs

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

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

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

-1

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

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

这可以很容易地修改,以符合特定的设置。 对于需要positionals(如果如“--help”在命令行给出的这将成为不需要的),我想出了以下内容:[positionals不允许一个required=...关键字ARG!]

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

基本上,如果指定了“--help”,则会将命令行中'pattern'出现次数从一次或多次变为零次或多次。

+0

我不介意downvote或两个,但我想知道为什么,尤其是因为另一个答案https://stackoverflow.com/a/44210638/26083(在这一点上upvote 5)基本上是一样的 – haavee 2017-11-03 13:13:43

7

我一直在寻找这种问题的一个简单的答案一段时间。所有你需要做的是检查是否'--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所用--argument接收用户是否无论TrueFalse依赖。已经测试过,似乎有效,并保证-a-b彼此之间有独立的行为。