使用 python argparse 模块首先报告无效选项(或使用正则表达式)
report invalid options first (or use regular expressions) with python argparse module
在 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
然而,由于不会首先报告错误的选项,因此很容易出错。例如:
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'])
将报告:
error: argument startTime: '5' is not a valid ISO-8601 time-stamp
当我希望它报告时更有用:
error: no such option: --gold
有可能实现吗?
在我看来,这是一个非常基本的用例。
直接编写参数解析器时,我通常使用模式
这样任何以 -- 选项前缀开头的未知选项都会被立即拒绝。
例如在 bash
# Process command-line arguments
while [ $# -gt 0 ]; do
case "" in
--debug)
DEBUGOPTION="--debug"
shift
break;;
--)
shift
break;;
--*)
handleUsageError ""
shift;;
*)
break;;
esac
done
我相信 argparse 在内部使用正则表达式,但我认为它们无法通过 add_argument()
访问
有什么方法可以轻松地用 argparse 做同样的事情吗?
简短的回答是 parse_args
使用 parse_known_args
。此方法可让您处理未知参数,例如 --gold
。因此,参数类型错误在 unknown arguments
错误之前出现。
我添加了一个涉及子classing ArgumentParser
和修改其调用堆栈深处的方法的解决方案。
我将尝试概述 parse_args
适用于您的示例。
它做的第一件事是将字符串分类为 O
或 A
。简单来说,-
开头的是O
,其他的是A
。它还会尝试将 O
个与定义的参数匹配。
在您的示例中,它找到 OAA
。正则表达式用于将此字符串与参数 nargs
定义的模式相匹配。 (如果需要我可以更详细地解释这一步)
--gold
不匹配;在某些时候(无论是在这个初始循环中还是之后)它被放入 extras
列表中。 (我会检查代码以获取详细信息)。
对于字符串的第二次循环,它交替尝试处理位置和可选。
在尝试将 5
与 starttime
匹配时,您的操作 class 会引发类型错误,该错误会传播到打印用法并退出。因为 --gold
未定义,所以 5
不作为可选参数使用。因此它被解析为第一个位置字符串。 (某些类型的可选参数采用 0 个参数,因此它不假设 --...
后面的任何内容都是可选参数)。
我认为,如果没有 5
,最后一个字符串将匹配。 parse_known_args
将 return 与 extras
项中的 --gold
。 parse_args
使用 parse_known_args
但当 extras
不为空时会引发错误。
所以在某种意义上,解析器确实检测到这两个错误,但它是 starttime
触发错误消息的错误。等到最后抱怨无法识别--gold
.
作为一般哲学,argparse
不会尝试检测和呈现所有错误。它不会收集错误列表以在最后的综合消息中显示。
我将查看代码以检查详细信息。我不认为你可以轻易地改变基本的解析模式。如果我想到一种方法来强制更早的 unrecognized option
错误,我将编辑此答案。
def _parse_optional(self, arg_string):
尝试 class 化一个 argv
字符串。如果字符串看起来像 positional
它 return 就是 None
。如果它匹配一个动作 option_string,它 return 是一个元组 '(action, option_string, None)` 和匹配的动作。最后如果不匹配,则returns:
# 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
我认为你的 --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
收集那个 '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)
如我之前所写,您的 --gold
被放在 extras
列表中,而 5
保留在可以解析为位置的参数列表中。
namespace
和 extras
通过 parse_known_args
传递给您(用户)或 parse_args
。
可以想象,您可以子class ArgumentParser
并定义修改后的_parse_optional
方法。它可能会引发错误,而不是 returning 那个 (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'])
生产
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
Subclass提供自定义操作是很好的argparse
(和Python)实践。
如果您想让 Python 开发人员更多地考虑这种情况,请考虑编写一个 bug/issue
(在 PEP 是为了更成熟的正式想法)。但是 argparse
bugs/patches 积压了很多,并且在向后兼容性方面非常谨慎。
是引用 _parse_optional
的 bug/issues 的列表。可能的变化包括如何处理不明确的可选值。 (我会扫描它们,看看我是否忘记了什么。一些补丁是我的。)但是通过使用 super
,我建议的更改不受函数内更改的影响。它仅受函数调用方式及其 returns 变化的影响,这种情况发生的可能性要小得多。通过提交你自己的问题,你至少让开发人员注意到有人依赖这个接口。
在 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
然而,由于不会首先报告错误的选项,因此很容易出错。例如:
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'])
将报告:
error: argument startTime: '5' is not a valid ISO-8601 time-stamp
当我希望它报告时更有用:
error: no such option: --gold
有可能实现吗? 在我看来,这是一个非常基本的用例。 直接编写参数解析器时,我通常使用模式 这样任何以 -- 选项前缀开头的未知选项都会被立即拒绝。 例如在 bash
# Process command-line arguments
while [ $# -gt 0 ]; do
case "" in
--debug)
DEBUGOPTION="--debug"
shift
break;;
--)
shift
break;;
--*)
handleUsageError ""
shift;;
*)
break;;
esac
done
我相信 argparse 在内部使用正则表达式,但我认为它们无法通过 add_argument()
访问有什么方法可以轻松地用 argparse 做同样的事情吗?
简短的回答是 parse_args
使用 parse_known_args
。此方法可让您处理未知参数,例如 --gold
。因此,参数类型错误在 unknown arguments
错误之前出现。
我添加了一个涉及子classing ArgumentParser
和修改其调用堆栈深处的方法的解决方案。
我将尝试概述 parse_args
适用于您的示例。
它做的第一件事是将字符串分类为 O
或 A
。简单来说,-
开头的是O
,其他的是A
。它还会尝试将 O
个与定义的参数匹配。
在您的示例中,它找到 OAA
。正则表达式用于将此字符串与参数 nargs
定义的模式相匹配。 (如果需要我可以更详细地解释这一步)
--gold
不匹配;在某些时候(无论是在这个初始循环中还是之后)它被放入 extras
列表中。 (我会检查代码以获取详细信息)。
对于字符串的第二次循环,它交替尝试处理位置和可选。
在尝试将 5
与 starttime
匹配时,您的操作 class 会引发类型错误,该错误会传播到打印用法并退出。因为 --gold
未定义,所以 5
不作为可选参数使用。因此它被解析为第一个位置字符串。 (某些类型的可选参数采用 0 个参数,因此它不假设 --...
后面的任何内容都是可选参数)。
我认为,如果没有 5
,最后一个字符串将匹配。 parse_known_args
将 return 与 extras
项中的 --gold
。 parse_args
使用 parse_known_args
但当 extras
不为空时会引发错误。
所以在某种意义上,解析器确实检测到这两个错误,但它是 starttime
触发错误消息的错误。等到最后抱怨无法识别--gold
.
作为一般哲学,argparse
不会尝试检测和呈现所有错误。它不会收集错误列表以在最后的综合消息中显示。
我将查看代码以检查详细信息。我不认为你可以轻易地改变基本的解析模式。如果我想到一种方法来强制更早的 unrecognized option
错误,我将编辑此答案。
def _parse_optional(self, arg_string):
尝试 class 化一个 argv
字符串。如果字符串看起来像 positional
它 return 就是 None
。如果它匹配一个动作 option_string,它 return 是一个元组 '(action, option_string, None)` 和匹配的动作。最后如果不匹配,则returns:
# 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
我认为你的 --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
收集那个 '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)
如我之前所写,您的 --gold
被放在 extras
列表中,而 5
保留在可以解析为位置的参数列表中。
namespace
和 extras
通过 parse_known_args
传递给您(用户)或 parse_args
。
可以想象,您可以子class ArgumentParser
并定义修改后的_parse_optional
方法。它可能会引发错误,而不是 returning 那个 (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'])
生产
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
Subclass提供自定义操作是很好的argparse
(和Python)实践。
如果您想让 Python 开发人员更多地考虑这种情况,请考虑编写一个 bug/issue
(在 PEP 是为了更成熟的正式想法)。但是 argparse
bugs/patches 积压了很多,并且在向后兼容性方面非常谨慎。
是引用 _parse_optional
的 bug/issues 的列表。可能的变化包括如何处理不明确的可选值。 (我会扫描它们,看看我是否忘记了什么。一些补丁是我的。)但是通过使用 super
,我建议的更改不受函数内更改的影响。它仅受函数调用方式及其 returns 变化的影响,这种情况发生的可能性要小得多。通过提交你自己的问题,你至少让开发人员注意到有人依赖这个接口。