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
接收True
或False
取决于用户是否使用--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
错误。但是调整x
的required
属性的基本想法应该可行。
好点。如果继承自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
(互斥)、and
、or
和 not
这样的测试。我认为那些很全面,但我无法用这些操作来表达你的情况。 (看起来我需要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.Action
或argparse.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 条件需要的参数的主要内容,如果未能解决你的问题,请参考以下文章