使用python argparse模块首先报告无效选项(或使用正则表达式)

Bru*_*ams 5 python argparse

在Python中使用argparse模块时,我正在寻找一种方法来捕获无效选项并更好地报告它们.https://docs.python.org/3/library/argparse.html#invalid-arguments上的文档提供了一个示例:

parser = argparse.ArgumentParser(prog='PROG'
parser.add_argument('--foo', type=int)
parser.add_argument('bar', nargs='?')

# invalid option
parser.parse_args(['--bar'])
usage: PROG [-h] [--foo FOO] [bar]
PROG: error: no such option: --bar
Run Code Online (Sandbox Code Playgroud)

然而,由于不首先报告错误的选项,所以很容易将其绊倒.例如:

import argparse
import datetime

def convertIsoTime(timestamp):
    """read ISO-8601 time-stamp using the AMS conventional format YYYY-MM-DDThh:mm:ssUTC"""
    try:
        return datetime.datetime.strptime(timestamp,"%Y-%m-%dT%H:%M:%SUTC")
    except:
        raise argparse.ArgumentTypeError("'{}' is not a valid ISO-8601 time-stamp".format(timestamp))

parser = argparse.ArgumentParser()
parser.add_argument('startTime', type=convertIsoTime)
parser.add_argument('--good', type=int,
                    help='foo')

args = parser.parse_args(['--gold','5','2015-01-01T00:00:00UTC'])
Run Code Online (Sandbox Code Playgroud)

将报告:

error: argument startTime: '5' is not a valid ISO-8601 time-stamp
Run Code Online (Sandbox Code Playgroud)

当我希望它报告更有用时:

error: no such option: --gold
Run Code Online (Sandbox Code Playgroud)

是否有可能实现这一目标?在我看来,这是一个非常基本的用例.在直接编写参数解析器时,我通常使用一种模式,使得以 - 选项前缀不是已知选项开头的任何内容都会立即被拒绝.例如在bash中

# Process command-line arguments
while [ $# -gt 0 ]; do
   case "$1" in
   --debug)
      DEBUGOPTION="--debug"
      shift
      break;;
   --)
      shift
      break;;
   --*)
      handleUsageError "$1"
      shift;;
   *)
      break;;
   esac
done
Run Code Online (Sandbox Code Playgroud)

我相信argparse在内部使用正则表达式,但我不认为它们可以通过add_argument()访问

有什么方法可以轻松地使用argparse做同等效果的吗?

hpa*_*ulj 1

简短的答案是parse_args使用parse_known_args. 此方法可让您处理未知参数,例如--gold. 因此,参数类型错误会在unknown arguments错误之前引发。

我添加了一个解决方案,涉及子类化ArgumentParser和修改其调用堆栈深处的方法。


我将尝试概述parse_args适用于您的示例的内容。

它所做的第一件事是将字符串分类为OA。简单地说,开头的-O,其他A。它还尝试将那些O与定义的参数进行匹配。

在您的示例中,它发现OAA. 正则表达式用于将此字符串与参数定义的模式进行匹配nargs。(如果需要我可以更详细地解释此步骤)

--gold不匹配; 在某个时刻(无论是在这个初始循环中还是稍后)它会被放入一个extras列表中。(我会检查代码以了解详细信息)。

对于字符串的第二次循环,它交替尝试处理位置和选项。

5当尝试与Action 类匹配时,starttime会引发类型错误,该错误会传播到打印用法并退出。因为--gold未定义,5所以不作为可选参数使用。因此它被解析为第一个位置字符串。(某些类型的可选参数采用 0 个参数,因此它不假设紧随其后的任何内容--...都是可选参数)。

我认为,如果没有5,最后一个字符串会匹配。 将在期限parse_known_args内返回。 使用但当不为空时会引发错误。--goldextrasparse_argsparse_known_argsextras

因此,从某种意义上说,解析器确实检测到这两个错误,但starttime触发错误消息的是它。它等到最后才抱怨无法识别--gold

作为一般哲学,argparse并不试图检测和呈现所有错误。它不会收集错误列表以在最终的综合消息中呈现。

我将查看代码以检查详细信息。我不认为你可以轻易改变基本的解析模式。如果我想到一种方法来强制出现较早的unrecognized option错误,我将编辑此答案。


def _parse_optional(self, arg_string):尝试对argv字符串进行分类。如果字符串看起来像 a,positional则返回None。如果它与操作 option_string 匹配,它将返回一个包含匹配操作的元组“(action, option_string, None)”。最后如果不匹配则返回:

    # it was meant to be an optional but there is no such option
    # in this parser (though it might be a valid option in a subparser)
    return None, arg_string, None
Run Code Online (Sandbox Code Playgroud)

我认为这就是你的情况--gold。请注意它可能仍然是有效选项的原因。

这个函数被调用

def _parse_known_args(self, arg_strings, namespace):
  ...
  for i, arg_string in enumerate(arg_strings_iter):
      ....
      option_tuple = self._parse_optional(arg_string)
      if option_tuple is None:
         pattern = 'A'
      else:
         option_string_indices[i] = option_tuple
         pattern = 'O'
      arg_string_pattern_parts.append(pattern)
  ...
  # at the end
  # return the updated namespace and the extra arguments
  return namespace, extras
Run Code Online (Sandbox Code Playgroud)

收集该'AOO'模式以及这些元组的列表。

在第二个循环期间,它在消耗位置和可选之间交替。消耗可选值的函数是:

def consume_optional(start_index):
    option_tuple = option_string_indices[start_index]
    action, option_string, explicit_arg = option_tuple
    if action is None:
       extras.append(arg_strings[start_index])
    ...otherwise...
       take_action(action, args, option_string)
Run Code Online (Sandbox Code Playgroud)

正如我之前所写,您的内容--gold被放入extras列表中,同时5保留在可以解析为位置的参数列表中。

namespaceextras传递parse_known_args给您(用户)或parse_args

可以想象,您可以子类化ArgumentParser并定义修改后的_parse_optional方法。它可能会引发错误而不是返回该(None, arg_string, None)元组。

import argparse
import datetime

class MyParser(argparse.ArgumentParser):
    def _parse_optional(self, arg_string):
        arg_tuple = super(MyParser, self)._parse_optional(arg_string)
        if arg_tuple is None:
            return arg_tuple  # positional
        else:
            if arg_tuple[0] is not None:
                return arg_tuple # valid optional
            else:
                msg = 'error: no such option: %s'%arg_string
                self.error(msg)

def convertIsoTime(timestamp):
    """read ISO-8601 time-stamp using the AMS conventional format YYYY-MM-DDThh:mm:ssUTC"""
    try:
        return datetime.datetime.strptime(timestamp,"%Y-%m-%dT%H:%M:%SUTC")
    except:
        raise argparse.ArgumentTypeError("'{}' is not a valid ISO-8601 time-stamp".format(timestamp))

# parser = argparse.ArgumentParser()
parser = MyParser()
parser.add_argument('startTime', type=convertIsoTime)
parser.add_argument('--good', type=int,
                    help='foo')

args = parser.parse_args(['--good','5','2015-01-01T00:00:00UTC'])
print(args)

args = parser.parse_args(['--gold','5','2015-01-01T00:00:00UTC'])
Run Code Online (Sandbox Code Playgroud)

产生

1505:~/mypy$ python3 stack31317166.py 
Namespace(good=5, startTime=datetime.datetime(2015, 1, 1, 0, 0))
usage: stack31317166.py [-h] [--good GOOD] startTime
stack31317166.py: error: error: no such option: --gold
Run Code Online (Sandbox Code Playgroud)

子类化以提供自定义操作是一个很好的argparse(和 Python)实践。

如果您希望 Python 开发人员更多地考虑这种情况,请考虑编写一个bug/issue (在 PEP 中是为了更成熟的正式想法)。但是有相当多的积压的argparse错误/补丁,并且对向后兼容性有很多谨慎的态度。


http://bugs.python.org/issue?%40columns=id%2Cactivity%2Ctitle%2Ccreator%2Cassignee%2Cstatus%2Ctype&%40sort=-activity&%40filter=status&%40action=searchid&ignore=file%3Acontent&%40search_text=_parse_optional&submit=搜索&状态=-1%2C1%2C2%2C3

是引用的错误/问题的列表_parse_optional。可能的更改包括如何处理不明确的选项。(我会扫描它们以查看是否忘记了任何内容。其中一些补丁是我的。)但是通过使用super,我建议的更改不会受到函数内更改的影响。它仅受函数调用方式及其返回内容的变化的影响,这种情况发生的可能性要小得多。通过提交您自己的问题,您至少让开发人员注意到有人依赖此接口。