【问题标题】:Comma separated inputs instead of space separated inputs for argparseargparse 的逗号分隔输入而不是空格分隔输入
【发布时间】:2023-04-05 00:00:02
【问题描述】:

我正在使用argparse 接收来自命令行的输入以运行我的脚本。

我当前的输入字符串如下所示:

path> python <\filename\\> -t T1 T2 T3 -f F1 F2

argparse 中是否有一个参数,这样我可以用逗号分隔输入,而不是用空格分隔输入?

换句话说:

path> python <\filename\\> -t T1,T2,T3 -f F1,F2

【问题讨论】:

    标签: python command-line-arguments


    【解决方案1】:

    argparse 中没有这样的功能。

    替代方案:

    • 后处理 args 命名空间并手动拆分/解析值
    • 定义自定义 action 并手动拆分/解析值
    • 定义自定义 type 并手动拆分/解析值
    • 子类ArgumentParser并自定义ArgumentParser.convert_arg_line_to_args

    【讨论】:

    • 感谢您的信息。很遗憾,作者或 argparse 做出了不使用逗号作为分隔符的可怕决定。它使代码难看。
    【解决方案2】:

    您可以使用模块shlex提取参数,然后将逗号替换为空格,并将结果传递给argparse进行进一步处理:

    comma_args = shlex.split("-t T1,T2,T3 -f F1,F2")
    # ['-t', 'T1,T2,T3', '-f', 'F1,F2']
    args = [x.replace(","," ") for x in comma_args]
    # ['-t', 'T1 T2 T3', '-f', 'F1 F2']
    parse_args(args)
    

    【讨论】:

      【解决方案3】:

      这里已经有一些有用的答案,但我想要更多:用逗号分隔,使用choices 验证值,并获得有用的错误消息,所以下面我提供了一个解决方案。

      简单版

      作为第一遍,我们可以将适当的函数传递给type 参数:

      >>> import argparse
      >>> parser = argparse.ArgumentParser(prog='cmd')
      >>> parser.add_argument('--foo', type=lambda arg: arg.split(','))
      >>> parser.parse_args(['--foo', 'a,b,c'])
      Namespace(foo=['a', 'b', 'c'])
      

      但这不适用于choices,因为它会检查整个列表是否在选择中,而不是每个值:

      >>> parser = argparse.ArgumentParser(prog='cmd')
      >>> parser.add_argument('--foo', type=lambda arg: arg.split(','), choices=('a', 'b', 'c'))
      >>> parser.parse_args(['--foo', 'a,b,c'])
      usage: cmd [-h] [--foo {a,b,c}]
      cmd: error: argument --foo: invalid choice: ['a', 'b', 'c'] (choose from 'a', 'b', 'c')
      

      nargs 设置为*+ 之类的值会检查每个值,但仅适用于以空格分隔的参数(例如--foo a b)而不是逗号分隔的参数。如果我们自己生成列表,似乎没有支持的方法来检查每个值是否在选项中。因此,我们需要通过type 参数(如 Shiplu Mokaddim partially implemented)自己提出错误。创建自定义 Action 类听起来很有希望,因为动作可以访问选项,但是动作发生在 type 函数应用并检查值之后,所以我们仍然无法使用 @987654335 add_argument() 上的 @ 参数用于此目的。

      更好的版本

      这是使用自定义type 函数的解决方案。我们这个函数接受一个有效选择的列表,但是由于类型转换的函数只能接受一个参数字符串,我们需要将它包装在一个类中(并定义特殊的__call__() 方法)或函数闭包。本解决方案使用后者。

      >>> def csvtype(choices):
      ...     """Return a function that splits and checks comma-separated values."""
      ...     def splitarg(arg):
      ...         values = arg.split(',')
      ...         for value in values:
      ...             if value not in choices:
      ...                 raise argparse.ArgumentTypeError(
      ...                     'invalid choice: {!r} (choose from {})'
      ...                     .format(value, ', '.join(map(repr, choices))))
      ...         return values
      ...     return splitarg
      >>> parser = argparse.ArgumentParser(prog='cmd')
      >>> parser.add_argument('--foo', type=csvtype(('a', 'b', 'c')))
      >>> parser.parse_args(['--foo', 'a,b,c'])
      Namespace(foo=['a', 'b', 'c'])
      >>> parser.parse_args(['--foo', 'a,b,d'])
      usage: cmd [-h] [-f F]
      cmd: error: argument -f: invalid choice: 'd' (choose from 'a', 'b', 'c')
      

      请注意,我们也会收到相应的错误。为此,请务必在函数内使用argparse.ArgumentTypeError 而不是argparse.ArgumentError

      其他选项

      用户 wim suggested 上面未讨论的其他一些选项。由于以下原因,我不认为这些具有吸引力:

      • 解析后对参数进行后处理意味着您必须做更多工作才能使错误消息与来自argparse 的错误消息保持一致。只需提高argparse.ArgumentError 就会导致堆栈跟踪。此外,argparse 会捕获解析期间出现的错误并更改它们以指定所使用的选项,否则您需要手动执行。

      • 子类化ArgumentParser 工作量更大,convert_arg_line_to_args() 用于从文件而非命令行读取参数。

      【讨论】:

        【解决方案4】:

        这里逗号分隔的输入实际上是一个不同的类型。您所要做的就是定义类型。 在这里,我定义了一个自定义类型。

        class DelimiterSeperatedInput:
          def __init__(self, item_type, separator=','):
            self.item_type = item_type
            self.separator = separator
        
          def __call__(self, value):
            values = []
            try:
              for val in value.split(self.separator):
                 typed_value = self.item_type(val)
                 values.append(typed_value)
            except Exception:
              raise ArgumentError("%s is not a valid argument" % value)
            return values
        
        parser.add_argument('-t', type=DelimiterSeperatedInput(str),
                            help='comma separated string values')
        parser.add_argument('-f', type=DelimiterSeperatedInput(float, ":"), 
                            help="colon separated floats')
        

        此代码可能无法按原样运行,您可能需要修复。但这是为了给出一个想法。

        注意:我可以通过使用listmap 等来减少__call__ 函数体。但是那样它就不是很可读了。一旦你有了这个想法,你就可以用它来做各种事情。

        【讨论】:

          【解决方案5】:

          如果您对空格不使用逗号分隔没问题,那么它内置于argparse

          In [1]: from argparse import ArgumentParser
          
          In [2]: parser = ArgumentParser()
          
          In [3]: parser.add_argument('-a', nargs='+')
          Out[3]: _StoreAction(option_strings=['-a'], dest='a', nargs='+', const=None, 
                               default=None, type=None, choices=None, help=None, metavar=None)
          
          In [4]: parser.parse_args(['-a', 'foo', 'bar'])
          Out[4]: Namespace(a=['foo', 'bar'])
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-08-17
            • 2012-07-15
            • 2020-12-09
            • 1970-01-01
            • 2018-12-10
            • 2023-02-07
            相关资源
            最近更新 更多