【问题标题】:python argparse - optional append argument with choicespython argparse - 带有选项的可选附加参数
【发布时间】:2014-12-20 01:26:18
【问题描述】:

我有一个脚本,我在其中询问用户要执行的预定义操作列表。我还希望能够在用户未定义任何内容时假设特定的操作列表。但是,似乎不可能同时完成这两项工作。

当用户不提供参数时,他们会收到默认选择无效的错误

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args([])
>>> usage: [-h] [{clear,copy,dump,lock} [{clear,copy,dump,lock} ...]]
: error: argument action: invalid choice: [['dump', 'clear']] (choose from 'clear', 'copy', 'dump', 'lock')

当他们确实定义了一组操作时,生成的命名空间会将用户的操作附加到默认值,而不是替换默认值

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args(['lock'])
args
>>> Namespace(action=[['dump', 'clear'], ['dump']])

【问题讨论】:

  • 我不确定这是否已被报告的类似错误解决:bugs.python.org/issue9625。处理此问题的一种可能方法是使用自定义操作,而不是 choices 关键字。请参阅this question 上已接受的答案

标签: python argparse


【解决方案1】:

您可以使用自定义的argparse.Action 来完成您需要的操作,如下例所示:

import argparse

parser = argparse.ArgumentParser()

class DefaultListAction(argparse.Action):
    CHOICES = ['clear','copy','dump','lock']
    def __call__(self, parser, namespace, values, option_string=None):
        if values:
            for value in values:
                if value not in self.CHOICES:
                    message = ("invalid choice: {0!r} (choose from {1})"
                               .format(value,
                                       ', '.join([repr(action)
                                                  for action in self.CHOICES])))

                    raise argparse.ArgumentError(self, message)
            setattr(namespace, self.dest, values)

parser.add_argument('actions', nargs='*', action=DefaultListAction,
                    default = ['dump', 'clear'],
                    metavar='ACTION')

print parser.parse_args([])
print parser.parse_args(['lock'])

脚本的输出是:

$ python test.py 
Namespace(actions=['dump', 'clear'])
Namespace(actions=['lock'])

【讨论】:

    【解决方案2】:

    在文档(http://docs.python.org/dev/library/argparse.html#default)中说:

    对于 nargs 等于的位置参数?或 *,当没有命令行参数时使用默认值。

    那么,如果我们这样做:

    acts = ['clear','copy','dump','lock']
    p = argparse.ArgumentParser()
    p.add_argument('action', nargs='*', choices=acts, default='clear')    
    print p.parse_args([])
    

    我们得到了我们所期望的

    Namespace(action='clear')
    

    问题是当您将列表作为默认值时。 但是我在文档中看到过,

    parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')
    

    所以,我不知道:-(

    无论如何,这是一个可以完成您想要的工作的解决方法:

    import sys, argparse
    acts = ['clear','copy','dump','lock']
    p = argparse.ArgumentParser()
    p.add_argument('action', nargs='*', choices=acts)
    args = ['dump', 'clear'] # I set the default here ... 
    if sys.argv[1:]:
        args = p.parse_args()
    print args
    

    【讨论】:

      【解决方案3】:

      我最终做了以下事情:

      • 没有append
      • 将空列表添加到可能的选项中,否则空输入会中断
      • 无默认
      • 之后检查一个空列表并在这种情况下设置实际默认值

      例子:

      parser = argparse.ArgumentParser()
      parser.add_argument(
          'is',
          type=int,
          choices=[[], 1, 2, 3],
          nargs='*',
      )
      
      args = parser.parse_args(['1', '3'])
      assert args.a == [1, 3]
      
      args = parser.parse_args([])
      assert args.a == []
      if args.a == []:
          args.a = [1, 2]
      
      args = parser.parse_args(['1', '4'])
      # Error: '4' is not valid.
      

      【讨论】:

        【解决方案4】:

        您可以测试用户是否正在提供操作(在这种情况下,将其解析为必需的位置参数),或者不提供任何操作(在这种情况下,将其解析为默认的可选参数):

        import argparse
        import sys
        
        acts = ['clear', 'copy', 'dump', 'lock']
        p = argparse.ArgumentParser()
        if sys.argv[1:]:
            p.add_argument('action', nargs = '*', choices = acts)
        else:
            p.add_argument('--action', default = ['dump', 'clear'])
        
        args = p.parse_args()
        print(args)
        

        运行时,产生以下结果:

        % test.py 
        Namespace(action=['dump', 'clear'])
        % test.py lock
        Namespace(action=['lock'])
        % test.py lock dump
        Namespace(action=['lock', 'dump'])
        

        您可能还有其他选项可以解析。在这种情况下,您可以使用parse_known_args 解析其他选项,然后在第二遍处理unknown 参数:

        import argparse
        
        acts = ['clear', 'copy', 'dump', 'lock']
        p = argparse.ArgumentParser()
        p.add_argument('--foo')
        args, unknown = p.parse_known_args()
        if unknown:
            p.add_argument('action', nargs = '*', choices = acts)
        else:
            p.add_argument('--action', default = ['dump', 'clear'])
        
        p.parse_args(unknown, namespace = args)
        print(args)
        

        运行时,产生以下结果:

        % test.py 
        Namespace(action=['dump', 'clear'], foo=None)
        % test.py --foo bar
        Namespace(action=['dump', 'clear'], foo='bar')
        % test.py lock dump
        Namespace(action=['lock', 'dump'], foo=None)
        % test.py lock dump --foo bar
        Namespace(action=['lock', 'dump'], foo='bar')
        

        【讨论】:

          【解决方案5】:

          由于您传递给 argparse 的“action='append'”参数,该操作被附加。

          去掉这个参数后,用户传过来的参数会自己显示出来,但是不传参数时程序会报错。

          向第一个参数添加“--”前缀可以最懒惰的方式解决此问题。

          acts = ['clear','copy','dump','lock']
          p = argparse.ArgumentParser()
          p.add_argument('--action', nargs='*', choices=acts, default=[['dump', 'clear']])
          args = p.parse_args()
          

          这种方法的缺点是用户传递的选项现在必须以'--action'开头,例如:

          app.py --action clear dump copy
          

          【讨论】:

            猜你喜欢
            • 2013-07-06
            • 1970-01-01
            • 2016-08-11
            • 2020-05-04
            • 2020-03-16
            • 2017-03-12
            • 2018-08-11
            • 2017-11-28
            • 2019-11-17
            相关资源
            最近更新 更多