[英]Python argparse IndexError for passing "-a="
我正在研究 Python 的argparse
(版本 3.6.7)的源代码。 如果您熟悉那段代码,将会很有帮助。 以下代码将导致此库引发IndexError
:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", action="store_true")
args = parser.parse_args("-a=".split())
print(args)
完整的错误信息是:
Traceback (most recent call last):
File "argparseRaiseIndexError.py", line 5, in <module>
args = parser.parse_args("-a=".split())
File "/usr/lib/python3.6/argparse.py", line 1743, in parse_args
args, argv = self.parse_known_args(args, namespace)
File "/usr/lib/python3.6/argparse.py", line 1775, in parse_known_args
namespace, args = self._parse_known_args(args, namespace)
File "/usr/lib/python3.6/argparse.py", line 1981, in _parse_known_args
start_index = consume_optional(start_index)
File "/usr/lib/python3.6/argparse.py", line 1881, in consume_optional
option_string = char + explicit_arg[0]
IndexError: string index out of range
如果我做对了,正确的行为是向用户报告错误输入的错误,例如“-a=”,而不是引发异常。
我查看了一段时间的代码,终于找出了原因。 这是因为'='
表示explicit_arg
,并且他们假设此字符串永远不会为空。 事实上,他们从来没有测试过,而是测试它是否是None
。 在触发异常的函数consume_optional
中,我们可以看到:
# if there is an explicit argument, try to match the
# optional's string arguments to only this
if explicit_arg is not None:
# bla bla...
option_string = char + explicit_arg[0] # Empty!
这是一个旧错误吗? 我应该如何报告这个错误?
这看起来像是几个病理案例的组合
In [177]: import argparse
In [178]: p = argparse.ArgumentParser()
In [179]: a1=p.add_argument('--aa')
In [180]: p.parse_args(['--a=10'])
Out[180]: Namespace(aa='10')
In [181]: p.parse_args(['--a='])
Out[181]: Namespace(aa='')
In [182]: a2=p.add_argument('--bb', action='store_true')
In [183]: p.parse_args(['--aa='])
Out[183]: Namespace(aa='', bb=False)
In [184]: p.parse_args(['--bb='])
usage: ipython3 [-h] [--aa AA] [--bb]
ipython3: error: argument --bb: ignored explicit argument ''
...
In [185]: a3=p.add_argument('-c')
In [186]: a4=p.add_argument('-d', action='store_true')
In [187]: p.parse_args(['-c='])
Out[187]: Namespace(aa=None, bb=False, c='', d=False)
In [188]: p.parse_args(['-d='])
...
-> 1881 option_string = char + explicit_arg[0]
1882 new_explicit_arg = explicit_arg[1:] or None
1883 optionals_map = self._option_string_actions
IndexError: string index out of range
它与-a
、简短的可选和store_true
据记载,长选项(带 --)可能采用 '=value',如In[180]
。 并且该值可能是 '',如In[181]
。
事实证明 '-c=' 也可以这样工作。 代码实际上并没有试图阻止使用短的 '=' ,即使它没有记录。 我依稀记得为另一个 SO 或 Python 错误问题调查过这个问题。
将“=”与“store_true”一起使用应该是错误的。 'store_true' 不带参数。 因此In[184]
引发了关于“显式论证”的正确错误。
short optional 与 long 真正不同的地方在于允许后续的short,例如
In [190]: p.parse_args(['-dc='])
Out[190]: Namespace(aa=None, bb=False, c='=', d=True)
所以你的错误发生是因为你使用了一个简短的可选'store_true'和'='。 所以有一系列的错误一起从裂缝中落下。 我将不得不更仔细地研究该函数以准确识别序列。
我同意正确的操作是引发ArgumentError
导致形式错误,如In[184]
。 但是因为它是几个错误的巧合造成的,我也忍不住建议忽略它。
你可以在https://bugs.python.org/报告它。 我已经尝试关注所有 argparse 错误,尽管我已经有一段时间没有提供正式的补丁了。
===
在_parse_optional(self, arg_string):
,'-a=' 导致它
return action, option_string, explicit_arg
(<the a action>, '-a', '')
(此函数不区分 '-a=' 和 '--aa=';有些人认为应该这样做。)
在consume_optional
,我们获取那个元组
action, option_string, explicit_arg = option_tuple
explicit_arg
不是 None, arg_count
是 0,它是一个单破折号选项,它“尝试解析出更多的单破折号选项”。
它将当前动作放回action_tuples
列表作为 (, [], 'a')
它尝试构造一个新的短可选,例如'-'+explicit_arg[0]
,同时将其余部分放回explicit_arg[1:]
。
通常这一步将-cdefoo
处理为 '-c'、'-d'、'-e=foo'。
这里有一些我不太了解的细节。 但是,如果这在 99.999% 的情况下有效,并且仅在三重病理情况下失败,我会犹豫要改变什么。 引入进一步错误或向后不兼容的可能性太大了。 没有全职开发人员在他的照顾下处理过argparse
。
我认为术语explicit_arg混合了两个不同的东西:一个由'=' 引入的参数(我称之为equal case )和一个argumnt 连接到一个短选项(我称之为sticky case )。 第一种情况允许 long 和 short 选项(--foo=bar 或 -f=bar),而后者仅对 short 选项有效(仅 -L/usr/lib)。 这里还有另一件事:空的explicit_arg 不是100% 错误,正如您所展示的。 当前导短选项不带参数时,代码试图重新解释explicit_arg 的含义,但不幸的是,第一种情况(相等情况)通过呈现空的explicit_arg 来干扰其工作。 这就是为什么我故意传递它"-a="
而不是"-afoo"
。 使用stick case,你不能渲染一个空的explicit_arg
(你不能对它进行split
),但在相同的情况下,你可以。 总之,这两种情况不应该从一开始就混在一起,或者至少应该在适当的时候加以区分。
同样的问题可能会导致其他兴趣错误:您可以欺骗库相信显式参数是一种选择! 见下文:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", action="store_true")
parser.add_argument("-b", action="store_false")
print(parser.parse_args("-a=b".split()))
将产生:
Namespace(a=True, b=False)
因为这里的'b'
是一个explict_arg
并且它的含义被重新解释为一个选项。 在我看来,正确的行为应该是:
usage: ShortOptioinWithEqualSignExplicitArg.py [-h] [-a]
ShortOptioinWithEqualSignExplicitArg.py: error: argument -a: ignored explicit argument 'b'
就像第二个add_argument
被注释掉一样。
总而言之,平等案件和棍棒案件的混合是主要问题。 为了解决这个问题,我的建议是将explicit_arg
概念拆分为sticky_arg
和equal_arg
。 只有在它是sticky_arg
时才重新解释以解析更短的选项。 这应该消除我在这里发现的两个错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.