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

Python:命令行参数--foo和--no foo,python,boolean,command-line-arguments,argparse,Python,Boolean,Command Line Arguments,Argparse,对于使用Python内置的argparse包解析布尔命令行选项,我知道这个问题及其几个答案: 一些答案(没错,IMO)指出布尔选项最常见和最直接的习惯用法(从调用者的角度来看)是同时接受--foo和--no foo选项,这将程序中的一些值分别设置为True或False 然而,在我看来,我能找到的所有答案实际上并没有正确地完成任务。它们似乎通常不符合以下条件之一: 可以设置适当的默认值(True、False或None) 为program.py--Help提供的帮助文本正确且有用,包括显示默认值 其

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

一些答案(没错,IMO)指出布尔选项最常见和最直接的习惯用法(从调用者的角度来看)是同时接受
--foo
--no foo
选项,这将程序中的一些值分别设置为
True
False

然而,在我看来,我能找到的所有答案实际上并没有正确地完成任务。它们似乎通常不符合以下条件之一:

  • 可以设置适当的默认值(
    True
    False
    None
  • program.py--Help
    提供的帮助文本正确且有用,包括显示默认值
  • 其中一个(我不在乎哪一个,但有时两者都是可取的):
    • 参数
      --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问题中显示的技术创建自定义操作:。这些操作会立即失败,原因是
    “error:argument--foo:expected one argument”
    ,或者(如果我设置
    nargs=0
    “ValueError:nargs for store actions必须大于0”
    。从插入
    argparse
    源代码来看,这似乎是因为预定义的“store\u const”、“store\u true”、“append”等之外的操作必须使用
    \u StoreAction
    类,该类需要参数

    有没有其他方法可以做到这一点?如果有人有一些我还没有想到的想法,请告诉我

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

    中的一个答案,特别是,包含从未被标准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)
    
    (这段代码在Python2和Python3中都可以使用)

    以下是实际情况:

    $ 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
    
    请注意,初始的
    用法
    消息没有提到
    --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)
    
    中的一个答案,特别是其中的一个答案,包含了一段从未被标准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)
    
    (这段代码在Python2和Python3中都可以使用)

    以下是实际情况:

    $ 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
    
    请注意,初始的
    用法
    消息没有提到
    --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)
    

    链接的
    类customAction(argparse.Action)
    子类
    Action
    \u StoreAction
    。是
    商店
    \uuuu init\uuuu
    抱怨
    nargs=0
    。注意
    store\u true
    subclasses
    store\u const
    .Update:看起来它现在已经被添加到
    argparse
    库中:链接的
    类customAction(argparse.Action)
    子类
    Action
    \u StoreAction
    。是
    商店
    \uuuu init\uuuu
    抱怨
    nargs=0
    。注意
    store\u true
    子类
    store\u const
    。更新:看起来它现在已经被添加到
    argparse
    库中:使用
    nargs=0
    时,
    type=bool
    是不必要的,不是吗?OP的第一个链接显示了使用此
    类型的陷阱
    ——唯一计算
    False
    的字符串是空字符串。至于使用问题,始终可以选择提供自定义
    使用
    参数。编写一个自动生成所需字符串的函数并不难。@hpaulj:是的,我认为在这里没有必要。我没有在arg列表中注意到它,因此没有将其拉出
    const=None
    choices=None
    也是毫无意义的,因为这是默认设置。我刚刚编辑了一下。谢谢@torek,我会玩的。当然,将其集成到
    argparse
    库本身是非常可取的,因此我也对您提到的问题进行了评论。对于
    nargs=0
    来说,
    type=bool
    是不必要的,不是吗?OP的第一个链接显示了使用此
    类型的陷阱
    ——唯一计算
    False
    的字符串是空字符串。至于使用问题,始终可以选择提供自定义
    使用
    参数。编写一个自动生成所需字符串的函数并不难。@hpaulj:是的,我认为这是不可能的