Python:命令行参数--foo和--no-foo

时间:2018-12-26 21:43:05

标签: python boolean command-line-arguments argparse

对于使用Python内置的argparse包解析布尔型命令行选项的情况,我知道这个问题及其几个答案:Parsing boolean values with argparse

几个答案(正确地,IMO)指出(从调用者的角度来看)布尔选项最常见,最直接的习惯用法是同时接受--foo--no-foo选项,将程序中的某些值分别设置为TrueFalse

但是,在我看来,我能找到的所有答案实际上并未正确完成任务。他们似乎通常无法满足以下条件之一:

  1. 可以设置适当的默认值(TrueFalseNone)。
  2. program.py --help给出的帮助文本是正确且有用的,包括显示默认设置。
  3. 中的任何一个(我都不在乎哪一个,但有时两者都是可取的):
    • 自变量--foo可以被后一个自变量--no-foo覆盖,反之亦然;
    • --foo--no-foo不兼容且互斥。

我想知道的是,即使使用argparse还是可以做到这一点。

根据@mgilson和@fnkr的回答,这是我最近来的

def add_bool_arg(parser, name, help_true, help_false, default=None, exclusive=True):
    if exclusive:
        group = parser.add_mutually_exclusive_group(required=False)
    else:
        group = parser
    group.add_argument('--' + name, dest=name, action='store_true', help=help_true)
    group.add_argument('--no-' + name, dest=name, action='store_false', help=help_false)
    parser.set_defaults(**{name: default})


parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
add_bool_arg(parser, 'foo', "Do foo", "Don't foo", exclusive=True)
add_bool_arg(parser, 'bar', "Do bar", "Don't bar", default=True, exclusive=False)

这可以很好地完成大多数事情,但是帮助文本令人困惑:

usage: argtest.py [-h] [--foo | --no-foo] [--bar] [--no-bar]

optional arguments:
  -h, --help  show this help message and exit
  --foo       Do foo (default: None)
  --no-foo    Don't foo (default: None)
  --bar       Do bar (default: True)
  --no-bar    Don't bar (default: True)

更好的帮助文字如下:

usage: argtest.py [-h] [--foo | --no-foo] [--bar] [--no-bar]

optional arguments:
  -h, --help      show this help message and exit
  --foo --no-foo  Whether to foo (default: None)
  --bar --no-bar  Whether to bar (default: True)

但是我看不到实现此目标的方法,因为必须始终将“-*”和“ --no- *”声明为单独的参数(对吗?)。

除了上面提到的SO问题的建议外,我还尝试使用另一个SO问题:Python argparse custom actions with additional arguments passed中显示的技术来创建自定义动作。这些失败后立即说出"error: argument --foo: expected one argument"或(如果我设置了nargs=0"ValueError: nargs for store actions must be > 0"失败。从进入argparse源开始,看起来是因为除预定义的“ store_const”,“ store_true”,“ append”等之外的其他操作必须使用_StoreAction类,这需要一个论点。

还有其他方法可以做到这一点吗?如果某人有我尚未想到的各种想法,请告诉我!

(顺便说一句,我正在创建这个新问题,而不是尝试添加到上面的第一个问题,因为上面的原始问题实际上是在要求一种处理--foo TRUE--foo FALSE参数的方法,这与IMO不太常见。)

1 个答案:

答案 0 :(得分:2)

your linked question中的一个答案,特别是Robert T. McGibbon中的一个答案,包含来自an enhancement request的代码片段,该片段从未被标准argparse接受。但是,如果您不介意烦恼,它的效果会很好。这是我的复制品,并做了一些小的修改,作为一个独立的模块,其中添加了一些pydoc字符串,以及其用法示例:

import argparse
import re

class FlagAction(argparse.Action):
    """
    GNU style --foo/--no-foo flag action for argparse
    (via http://bugs.python.org/issue8538 and
    https://stackoverflow.com/a/26618391/1256452).

    This provides a GNU style flag action for argparse.  Use
    as, e.g., parser.add_argument('--foo', action=FlagAction).
    The destination will default to 'foo' and the default value
    if neither --foo or --no-foo are specified will be None
    (so that you can tell if one or the other was given).
    """
    def __init__(self, option_strings, dest, default=None,
                 required=False, help=None, metavar=None,
                 positive_prefixes=['--'], negative_prefixes=['--no-']):
        self.positive_strings = set()
        # self.negative_strings = set()
        # Order of strings is important: the first one is the only
        # one that will be shown in the short usage message!  (This
        # is an annoying little flaw.)
        strings = []
        for string in option_strings:
            assert re.match(r'--[a-z]+', string, re.IGNORECASE)
            suffix = string[2:]
            for positive_prefix in positive_prefixes:
                s = positive_prefix + suffix
                self.positive_strings.add(s)
                strings.append(s)
            for negative_prefix in negative_prefixes:
                s = negative_prefix + suffix
                # self.negative_strings.add(s)
                strings.append(s)
        super(FlagAction, self).__init__(option_strings=strings, dest=dest,
                                         nargs=0, default=default,
                                         required=required, help=help,
                                         metavar=metavar)

    def __call__(self, parser, namespace, values, option_string=None):
        if option_string in self.positive_strings:
            setattr(namespace, self.dest, True)
        else:
            setattr(namespace, self.dest, False)


if __name__ == '__main__':
    p = argparse.ArgumentParser()
    p.add_argument('-a', '--arg', help='example')
    p.add_argument('--foo', action=FlagAction, help='the boolean thing')
    args = p.parse_args()
    print(args)

(此代码在Python 2和3中均适用)。

这是正在发生的事情:

$ python flag_action.py -h
usage: flag_action.py [-h] [-a ARG] [--foo]

optional arguments:
  -h, --help         show this help message and exit
  -a ARG, --arg ARG  example
  --foo, --no-foo    the boolean thing

请注意,初始usage消息没有提及--no-foo选项。除了使用您不喜欢的分组方法之外,没有其他简单的方法可以纠正此问题。

$ python flag_action.py -a something --foo
Namespace(arg='something', foo=True)
$ python flag_action.py --no-foo
Namespace(arg=None, foo=False)