使用optparse或argparse检测命令行选项是否被多次指定

4

Python的optparse通常允许用户多次指定选项,而只保留最后一次出现的选项值。例如,如果选项--foo的作用是store,而选项--flag的作用是store_conststore_truestore_false,则以下命令等效:

my-command --foo=bar --foo=another --flag --foo=last --flag
my-command --flag --foo=last

更新:argparse默认也会执行同样的操作。
现在,我有很多选项,指定其中任何一个选项超过一次都是没有意义的。如果用户指定了相同的选项超过一次,我想警告他们可能出现的错误。
最优雅的检测重复选项的方法是什么?请注意,同一个选项可以有短格式、长格式和缩写长格式(例如,-f--foobar--foob--foo都是相同的选项)。如果还能检测到同时指定了具有相同目的地的多个选项的情况,那就更好了,这样就可以在用户同时指定--quiet--verbose并且两个选项存储到同一个目的地时发出警告,并有效地覆盖彼此。
更新:为了更加用户友好,警告应该涉及到在命令行上使用的确切选项名称。使用“追加”操作而不是“存储”操作是可能的,但当我们检测到冲突时,我们无法确定哪些选项造成了冲突(是-q--verbose还是--quiet --quiet?)。
不幸的是,我被困在optparse中,无法使用argparse,因为我必须支持Python 2.6。
附注:如果您知道仅适用于argparse的解决方案,请也发布出来。虽然我试图将外部依赖的数量最小化,但在Python 2.6下使用argparse仍然是一个选项。

2
你可以将 argparse 打包在你的模块中,或者将其作为依赖项。 - Jakob Bowyer
argparse 可在 Python 2.3 及更高版本上使用。 - Martijn Pieters
好的,我该如何使用argparse实现这个? - Till Ulen
2个回答

1

我认为正确的方法是以某种方式“定义您的操作”。

例如,您可以使用动作{{link1:callback}}并实现一个函数来实现所需的行为。 您可以编写一个函数,首先检查目标是否已经填充,如果已经填充,则将重叠选项存储到列表中。 解析完成后,应检查这些列表是否为空,如果不为空,则引发适当的异常。

另一种方法是定义自己的操作。您可以在此处查看。

使用回调的小例子:

import sys
import functools
from optparse import OptionParser


bad_option = 'BAD OPTION'

def store(option, opt, value, parser, dest, val):
    """Set option's destination *dest* to *val*  if there are no conflicting options."""
    list_name = dest + '_options_list'
    try:
        # if this option is a conflict, save its name and set the value to bad_option
        getattr(parser.values, list_name).append(opt)
        setattr(parser.values, dest, bad_option)
    except AttributeError:
        # no conflicts, set the option value and add the options list
        setattr(parser.values, dest, val)
        setattr(parser.values, list_name, [opt])

store_true = functools.partial(store, val=True)
store_false = functools.partial(store, val=False)


parser = OptionParser()
parser.add_option('-v', '--verbose',
                  action='callback', callback=store_true,
                  help='Increase output verbosity',
                  callback_kwargs={'dest': 'verbose'})

parser.add_option('-q', '--quiet',
                  action='callback', callback=store_false,
                  help='Decrease output verbosity',
                  callback_kwargs={'dest': 'verbose'})

opts, args = parser.parse_args()

# detects all conflicting options for all destinations
found = False
for dest in ('verbose',):
    if getattr(opts, dest) == bad_option:
        conflicting_opts = ', '.join(getattr(opts, dest + '_options_list'))
        print('Conflicting options %s for destination %s'
              % (conflicting_opts, dest))
        found = True

if found:
    parser.print_usage()
    sys.exit(2)

并输出:

$ python testing_optparse.py -v -q
Conflicting options -v, -q for destination verbose
Usage: prova_optparse.py [options]

也许在检测到冲突时引发一个OptionValueError会更好,即使这样只能获取几个冲突选项。如果您想获取所有冲突的选项,则必须解析剩余的参数(在parser.rargs中)。


1

这是可能的,虽然不完美且有点脆弱。将所有的“store”操作更改为“append”。在解析选项后,迭代Values对象的所有属性(它们在其__dict__中),并用其最后一个元素替换每个非空列表。如果列表包含多个元素,则发出警告。(糟糕,在此时我们不知道导致冲突的确切选项名称。)如果列表为空,请用该目标的默认值替换它(手动进行,我们不能再使用optparse默认机制)。跳过的目标的停止列表可能会很方便。 - Till Ulen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接