【问题标题】:In Python argparse, is it possible to have paired --no-something/--something arguments?在 Python argparse 中,是否可以配对 --no-something/--something 参数?
【发布时间】:2012-03-03 07:18:44
【问题描述】:

我正在编写一个程序,我希望在其中有这样的参数:

--[no-]foo   Do (or do not) foo. Default is do.

有没有办法让 argparse 为我做这件事?

2012 年我在这里使用 Python 3.2。

事实证明,BooleanOptionalAction 已添加到 Python 3.9 的 argparse 版本中并解决了这个问题,我已将我接受的答案更改为新答案。但是,如果您仍在使用 3.9 之前的 Python,此页面上的其他答案应该会有所帮助。

【问题讨论】:

  • 没有。 “no-”前缀是高度本地化的。它在英语中并不一致(“un-”也很常见。)
  • 我认为你必须自己写。我希望它是内置的。
  • @S.Lott:确实如此。不过,这个节目不会有全球观众。 :-) 如果有这种可能性,我希望能够以某种方式自定义前缀。
  • 全球不是问题。语言是问题。对于我熟悉的一种语言,有无数的违规行为。这就是为什么没有“自动”功能的原因。
  • @jterrace:我希望_add_action API 被记录在案,并且Action 不仅仅是一个简单的属性容器。

标签: python python-3.x argparse


【解决方案1】:

嗯,由于各种原因,到目前为止,没有一个答案是令人满意的。所以这是我自己的答案:

class ActionNoYes(argparse.Action):
    def __init__(self, opt_name, dest, default=True, required=False, help=None):
        super(ActionNoYes, self).__init__(['--' + opt_name, '--no-' + opt_name], dest, nargs=0, const=None, default=default, required=required, help=help)
    def __call__(self, parser, namespace, values, option_string=None):
        if option_string.starts_with('--no-'):
            setattr(namespace, self.dest, False)
        else:
            setattr(namespace, self.dest, True)

还有一个使用示例:

>>> p = argparse.ArgumentParser()
>>> p._add_action(ActionNoYes('foo', 'foo', help="Do (or do not) foo. (default do)"))
ActionNoYes(option_strings=['--foo', '--no-foo'], dest='foo', nargs=0, const=None, default=True, type=None, choices=None, help='Do (or do not) foo. (default do)', metavar=None)
>>> p.parse_args(['--no-foo', '--foo', '--no-foo'])
Namespace(foo=False)
>>> p.print_help()
usage: -c [-h] [--foo]

optional arguments:
  -h, --help       show this help message and exit
  --foo, --no-foo  Do (or do not) foo. (default do)

不幸的是,_add_action 成员函数没有记录在案,因此就 API 支持而言,这不是“官方”的。另外,Action 主要是一个持有者类。它本身的行为很少。如果可以使用它来自定义帮助消息,那就太好了。例如在开头说--[no-]foo。但那部分是由Action 类之外的东西自动生成的。

【讨论】:

  • 您也许可以使用metavar 选项以某种方式获取--[no-]foo
  • 不应该是startswith而不是starts_with吗?
  • 这太棒了!但是你为什么打电话是ActionNoYes?我有一个短暂的大脑段错误:D
  • @corwin.amber - 你认为应该是 ActionYesNo? :-) 我不记得我为什么选择那个订单了。毕竟是 8 年前的事了。
  • 是的,大声笑...我试图在我的代码中创建一个ActionYesNo,盯着错误消息看不到它为什么找不到它...也许只是我。无论如何,您的回答和@btel 的改编非常有用。
【解决方案2】:

我修改了@Omnifarious 的解决方案,让它更像标准动作:

import argparse

class ActionNoYes(argparse.Action):
    def __init__(self, option_strings, dest, default=None, required=False, help=None):

        if default is None:
            raise ValueError('You must provide a default with Yes/No action')
        if len(option_strings)!=1:
            raise ValueError('Only single argument is allowed with YesNo action')
        opt = option_strings[0]
        if not opt.startswith('--'):
            raise ValueError('Yes/No arguments must be prefixed with --')

        opt = opt[2:]
        opts = ['--' + opt, '--no-' + opt]
        super(ActionNoYes, self).__init__(opts, dest, nargs=0, const=None, 
                                          default=default, required=required, help=help)
    def __call__(self, parser, namespace, values, option_strings=None):
        if option_strings.startswith('--no-'):
            setattr(namespace, self.dest, False)
        else:
            setattr(namespace, self.dest, True)

您可以像添加任何标准选项一样添加是/否参数。你只需要在action 参数中传递ActionNoYes 类:

parser = argparse.ArgumentParser()
parser.add_argument('--foo', action=ActionNoYes, default=False)

现在当你调用它时:

>> args = parser.parse_args(['--foo'])
Namespace(foo=True)
>> args = parser.parse_args(['--no-foo'])
Namespace(foo=False)
>> args = parser.parse_args([])
Namespace(foo=False)  

【讨论】:

    【解决方案3】:

    argparseadd_mutually_exclusive_group() 有帮助吗?

    parser = argparse.ArgumentParser()
    exclusive_grp = parser.add_mutually_exclusive_group()
    exclusive_grp.add_argument('--foo', action='store_true', help='do foo')
    exclusive_grp.add_argument('--no-foo', action='store_true', help='do not do foo')
    args = parser.parse_args()
    
    print 'Starting program', 'with' if args.foo else 'without', 'foo'
    print 'Starting program', 'with' if args.no_foo else 'without', 'no_foo'
    

    这是运行时的样子:

    ./so.py --help
    usage: so.py [-h] [--foo | --no-foo]
    
    optional arguments:
      -h, --help  show this help message and exit
      --foo       do foo
      --no-foo    do not do foo
    
    ./so.py
    Starting program without foo
    Starting program without no_foo
    
    ./so.py --no-foo --foo
    usage: so.py [-h] [--foo | --no-foo]
    so.py: error: argument --foo: not allowed with argument --no-foo
    

    这与互斥组中的以下内容不同,在您的程序中允许 neither 选项(我假设您想要 options 因为--句法)。这意味着其中之一:

    parser.add_argument('--foo=', choices=('y', 'n'), default='y',
                        help="Do foo? (default y)")
    

    如果这些是必需的(非可选的),也许使用 add_subparsers() 是您正在寻找的。​​p>

    更新 1

    逻辑上不同,但可能更简洁:

    ...
    exclusive_grp.add_argument('--foo', action='store_true', dest='foo', help='do foo')
    exclusive_grp.add_argument('--no-foo', action='store_false', dest='foo', help='do not do foo')
    args = parser.parse_args()
    
    print 'Starting program', 'with' if args.foo else 'without', 'foo'
    

    并运行它:

    ./so.py --foo
    Starting program with foo
    ./so.py --no-foo
    Starting program without foo
    ./so.py
    Starting program without foo
    

    【讨论】:

    • 您能否为--no-foo 设置action='store_false' 并为两者设置dest='foo' 以便它显示在一个变量中?
    • @jterrace 是的。有趣的建议。我添加了一个更新的解决方案。
    • 不错。你可以把它包装在像@s-lott's answer这样的函数中,真的很好
    • 这很好,只是帮助有点不必要的冗长。但至少论点组的事情保持相关的论点粘在一起。
    • 另外,它不允许同时指定 '--foo' 和 '--no-foo' 并且最后指定的优先。
    【解决方案4】:

    编写你自己的子类。

    class MyArgParse(argparse.ArgumentParser):
        def magical_add_paired_arguments( self, *args, **kw ):
            self.add_argument( *args, **kw )
            self.add_argument( '--no'+args[0][2:], *args[1:], **kw )
    

    【讨论】:

    • 嗯...这是一个有趣的想法。是否有一个“参数对象”的想法可以自己解析事物并可能生成它自己的帮助消息?那真的可以解决问题。
    • @Omnifarious:“生成它自己的帮助信息”?这可能意味着什么?如上所示添加更多代码有什么问题?如果您想要更神奇的事情发生,您可能会发现自己阅读源代码至argparse 并了解其内部工作原理会更容易。
    • 嗯,这是 argparse 的一大优势。它会为您生成帮助消息和内容。 add_argument 可以被认为是一个构造某种参数对象的函数,该对象代表参数的所有特征......如何解析它,将其填充到哪个变量中,默认值,如何生成帮助,所有这些东西,并将其放入解析器中的一个不错的列表中。但你是对的,我应该自己钻研内部结构,看看我能不能按照我想要的方式摆弄它。如果它不像我想象的那样工作,它应该。它更加灵活。
    • “add_argument 可以被认为是一个函数”?它一种构造参数对象的方法。这就是它的实际作用。我不明白评论。你在说什么?
    • 所以它确实按我想象的方式工作。构造了一个“参数对象”。这意味着您可以实例化一个不同的参数对象,以不同的方式实现这些方法。例如,它可以将包含所有给定给 add_argument 方法的值的字典添加到这些字典的列表中。
    【解决方案5】:

    为了好玩,这里是S.Lott's answer的完整实现:​​

    import argparse
    
    class MyArgParse(argparse.ArgumentParser):
        def magical_add_paired_arguments( self, *args, **kw ):
            exclusive_grp = self.add_mutually_exclusive_group()
            exclusive_grp.add_argument( *args, **kw )
            new_action = 'store_false' if kw['action'] == 'store_true' else 'store_true'
            del kw['action']
            new_help = 'not({})'.format(kw['help'])
            del kw['help']
            exclusive_grp.add_argument( '--no-'+args[0][2:], *args[1:], 
                               action=new_action,
                               help=new_help, **kw )
    
    parser = MyArgParse()
    parser.magical_add_paired_arguments('--foo', action='store_true',
                                        dest='foo', help='do foo')
    args = parser.parse_args()
    
    print 'Starting program', 'with' if args.foo else 'without', 'foo'
    

    这是输出:

    ./so.py --help
    usage: so.py [-h] [--foo | --no-foo]
    
    optional arguments:
      -h, --help  show this help message and exit
      --foo       do foo
      --no-foo    not(do foo)
    

    【讨论】:

    • 这很好,但有几个缺点。首先,它允许在命令行上同时指定--foo--no-foo,并让最后一个优先。其次,帮助是不必要的冗长,即使相互排斥的组事物确实将它们放在一起。我走自己的路,在回答这个问题时详细说明了我的方法。
    【解决方案6】:

    扩展https://stackoverflow.com/a/9236426/1695680的回答

    import argparse
    
    class ActionFlagWithNo(argparse.Action):
        """
            Allows a 'no' prefix to disable store_true actions.
            For example, --debug will have an additional --no-debug to explicitly disable it.
        """
        def __init__(self, opt_name, dest=None, default=True, required=False, help=None):
            super(ActionFlagWithNo, self).__init__(
                [
                    '--' + opt_name[0],
                    '--no-' + opt_name[0],
                ] + opt_name[1:],
                dest=(opt_name[0].replace('-', '_') if dest is None else dest),
                nargs=0, const=None, default=default, required=required, help=help,
            )
    
        def __call__(self, parser, namespace, values, option_string=None):
            if option_string.startswith('--no-'):
                setattr(namespace, self.dest, False)
            else:
                setattr(namespace, self.dest, True)
    
    class ActionFlagWithNoFormatter(argparse.HelpFormatter):
        """
            This changes the --help output, what is originally this:
    
                --file, --no-file, -f
    
            Will be condensed like this:
    
                --[no-]file, -f
        """
    
        def _format_action_invocation(self, action):
            if action.option_strings[1].startswith('--no-'):
                return ', '.join(
                    [action.option_strings[0][:2] + '[no-]' + action.option_strings[0][2:]]
                    + action.option_strings[2:]
                )
            return super(ActionFlagWithNoFormatter, self)._format_action_invocation(action)
    
    
    def main(argp=None):
        if argp is None:
            argp = argparse.ArgumentParser(
                formatter_class=ActionFlagWithNoFormatter,
            )
            argp._add_action(ActionFlagWithNo(['flaga', '-a'], default=False, help='...'))
            argp._add_action(ActionFlagWithNo(['flabb', '-b'], default=False, help='...'))
    
            argp = argp.parse_args()
    

    这会产生如下帮助输出:

    usage: myscript.py [-h] [--flaga] [--flabb]
    
    optional arguments:
      -h, --help        show this help message and exit
      --[no-]flaga, -a  ...
      --[no-]flabb, -b  ...
    

    这里是 Gist 版本,欢迎请求请求 :) https://gist.github.com/thorsummoner/9850b5d6cd5e6bb5a3b9b7792b69b0a5

    【讨论】:

      【解决方案7】:

      v3.9 添加了一个action 类来执行此操作。来自the docs(接近action 部分的末尾)

      BooleanOptionalActionargparse 中可用,并增加了对布尔操作的支持,例如--foo--no-foo

      >>> import argparse
      >>> parser = argparse.ArgumentParser()
      >>> parser.add_argument('--foo', action=argparse.BooleanOptionalAction)
      >>> parser.parse_args(['--no-foo'])
      Namespace(foo=False)
      

      【讨论】:

        【解决方案8】:

        在看到这个问题和答案之前,我编写了自己的函数来处理这个问题:

        def on_off(item):
            return 'on' if item else 'off'
        
        def argparse_add_toggle(parser, name, **kwargs):
            """Given a basename of an argument, add --name and --no-name to parser
        
            All standard ArgumentParser.add_argument parameters are supported
            and fed through to add_argument as is with the following exceptions:
            name     is used to generate both an on and an off
                     switch: --<name>/--no-<name>
            help     by default is a simple 'Switch on/off <name>' text for the
                     two options. If you provide it make sure it fits english
                     language wise into the template
                       'Switch on <help>. Default: <default>'
                     If you need more control, use help_on and help_off
            help_on  Literally used to provide the help text for  --<name>
            help_off Literally used to provide the help text for  --no-<name>
            """
            default = bool(kwargs.pop('default', 0))
            dest = kwargs.pop('dest', name)
            help = kwargs.pop('help', name)
            help_on  = kwargs.pop('help_on',  'Switch on {}. Default: {}'.format(help, on_off(defaults)))
            help_off = kwargs.pop('help_off', 'Switch off {}.'.format(help))
        
            parser.add_argument('--' + name,    action='store_true',  dest=dest, default=default, help=help_on)
            parser.add_argument('--no-' + name, action='store_false', dest=dest, help=help_off)
        

        可以这样使用:

        defaults = {
            'dry_run' : 0,
            }
        
        parser = argparse.ArgumentParser(description="Fancy Script",
                                         formatter_class=argparse.RawDescriptionHelpFormatter)
        argparse_add_toggle(parser, 'dry_run', default=defaults['dry_run'],
                            help_on='No modifications on the filesystem. No jobs started.',
                            help_off='Normal operation')
        parser.set_defaults(**defaults)
        
        args = parser.parse_args()
        

        帮助输出如下所示:

          --dry_run             No modifications on the filesystem. No jobs started.
          --no-dry_run          Normal operation
        

        我更喜欢将 argparse.Action 子类化的方法,因为它使使用它的代码更简洁,更易于阅读。

        这段代码的优点是有一个标准的默认帮助,还有一个help_onhelp_off 来重新配置相当愚蠢的默认值。

        也许有人可以整合。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-10-02
          相关资源
          最近更新 更多