【问题标题】:Why isn't fromfile-prefix-chars in Python argparse working?为什么 Python argparse 中的 fromfile-prefix-chars 不起作用?
【发布时间】:2014-09-24 22:31:12
【问题描述】:

我正在尝试使用 Python 中 argparse 的 fromfile-prefix-chars 功能从文件中加载我的所有命令行参数,但它一直抱怨我没有指定一些参数。

代码:

import argparse

def go():
   parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
   parser.add_argument("--option1")
   parser.add_argument("--option2", type=int, required=True)
   args = parser.parse_args()

if __name__ == "__main__":
    go()

参数文件:

--option1 foo
--option2 1234

命令行和输出:

$ python testargparse.py @testargs
usage: testargparse.py [-h] [--option1 OPTION1] --option2 OPTION2
testargparse.py: error: argument --option2 is required

您可以看到我在文件中提供了所需的参数,但 argparse 没有看到它。

【问题讨论】:

    标签: python python-2.7 argparse


    【解决方案1】:

    问题是,当在文件中指定时,每个参数必须在它和选项名称之间有一个“=”。虽然从命令行运行时 argparse 在该格式上更加灵活(其中空格或 = 可以),但从文件运行时它必须有一个 '='。

    因此,一个有效的参数文件将是:

    --option1=foo
    --option2=1234
    

    还有一点需要注意,确保行尾没有多余的空格,否则 argparse 读取文件时,选项中会包含空格。

    【讨论】:

      【解决方案2】:

      来自文档:

      默认情况下,从文件中读取的参数必须是每行一个......并且被视为与命令行上的原始文件引用参数位于同一位置。所以在上面的例子中,表达式 ['-f', 'foo', '@args.txt'] 被认为等同于表达式 ['-f', 'foo', '-f', 'bar'] .

      在示例中:

      fp.write('-f\nbar')
      

      所以文件包含:

      -f
      bar
      

      换句话说,每个文件行对应于命令行中的一个“单词”(空格分隔)。 --option1=foo 是一个字。 --option1 foo 的解释就像它在命令行中被引用一样,例如。 prog.py '--option1 foo' '--option2 1234'

      https://docs.python.org/dev/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args 有一个自定义函数,可以在空格上分割行。如果您想坚持使用参数文件,请尝试一下。


      import argparse
      
      with open('args.txt', 'w') as fp:
          fp.write('--option1 foo\n--option2 1234')  # error
          # but works with modifed 'convert...'
          #fp.write('--option1=foo\n--option2=1234')  # works
          #fp.write('--option1\nfoo\n--option2\n1234') # works
      
      def convert_arg_line_to_args(arg_line):
          for arg in arg_line.split():
              if not arg.strip():
                  continue
              yield arg
      """
      default line converter:
      def convert_arg_line_to_args(self, arg_line):
          return [arg_line]
      """
      
      def go():
         parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
         parser.convert_arg_line_to_args = convert_arg_line_to_args
         parser.add_argument("--option1")
         parser.add_argument("--option2", type=int, required=True)
         args = parser.parse_args(['@args.txt'])
         print args
      
      if __name__ == "__main__":
          go()
      

      @toes 建议使用shlex 来解析文件。 shlex 有一个很好的功能,它去掉了不必要的引号。

      shlex 可用于分割文件的各个行。

      def sh_split(arg_line):
          for arg in shlex.split(arg_line):
              yield arg
      parser.convert_arg_line_to_args = sh_split
      

      或者它可以替换整个@file读取方法(_read_args_from_files)-这应该与@toes答案相同,除了@file字符串可以在命令行中的任何位置(甚至可以重复)。

      def at_read_fn(arg_strings):
          # expand arguments referencing files
          new_arg_strings = []
          for arg_string in arg_strings:
              if not arg_string or not arg_string.startswith('@'):
                  new_arg_strings.append(arg_string)
              else:
                  with open(arg_string[1:]) as args_file:
                      arg_strings = shlex.split(args_file.read())
                      new_arg_strings.extend(arg_strings)
          return new_arg_strings
      parser._read_args_from_files = at_read_fn
      

      显然,更清洁的生产版本会在 ArgumentParser 子类中修改这些方法。

      【讨论】:

      • 我很欣赏这个答案中的细节,但我认为这对于只希望他们的参数文件工作的用户来说有点过分了。我认为应该强调最简单的解决方案是在文件中添加一个“=”。如果您将我的答案与您的答案结合起来(预先使用简单的解决方案),我认为会更好并且会接受。
      • 文档的一个可能问题是“参数”的含义不明确。您可能一直认为--option1 foo 是一个参数,或可选+参数。但是文档真正的意思是argument stringargv 被划分的字符串。实际上,文件中的字符串会在任何真正的解析开始之前插入到argv
      【解决方案3】:

      试试这个方法

      # encoding: utf-8
      import imp
      import argparse
      
      
      class LoadConfigAction(argparse._StoreAction):
          NIL = object()
      
          def __init__(self, option_strings, dest, **kwargs):
              super(self.__class__, self).__init__(option_strings, dest)
              self.help = "Load configuration from file"
      
          def __call__(self, parser, namespace, values, option_string=None):
              super(LoadConfigAction, self).__call__(parser, namespace, values, option_string)
      
              config = imp.load_source('config', values)
      
              for key in (set(map(lambda x: x.dest, parser._actions)) & set(dir(config))):
                  setattr(namespace, key, getattr(config, key))
      

      用法

      parser.add_argument("-C", "--config", action=LoadConfigAction, default=None)
      parser.add_argument("-H", "--host")
      

      示例配置(实际是python文件)

      # Config example /etc/my.conf
      import os
      
      # Parameter definition
      host = os.getenv("HOST", "127.0.0.1")
      

      【讨论】:

        【解决方案4】:

        我认为对此有更好的答案:使用 shlex。

        if sys.argv[1].startswith('@'):
            args = parser.parse_args( shlex.split(open(sys.argv[1][1:]).read()) )
        else:
            args = parser.parse_args()
        

        这允许您以更自然的方式在文件中指定参数,例如,它允许使用空格等号在单行上指定参数,如下所示:

        arg1
        arg2
        --opt1 'foo'
        --opt2='bar'
        

        shlex.split 按照您的预期将其拆分:

        ['arg1', 'arg2', '--opt1', 'foo', '--opt2=bar']
        

        这个方法唯一没有的是它期望@file.txt 是第一个参数。

        【讨论】:

        • 您可以将shlex.split 与我描述的convert_arg_line_to_args 方法结合使用。这将在出现@ 字符串的任何位置拼接值。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-28
        • 2011-09-30
        • 1970-01-01
        • 2016-08-12
        • 1970-01-01
        • 1970-01-01
        • 2011-10-13
        相关资源
        最近更新 更多