我正在研究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!
这是一个旧错误吗?我应该如何报告此错误?
答案 0 :(得分:1)
这看起来像是几个病理病例的组合
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可选与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,它是一个单破折号选项,它“试图解析出更多的单破折号选项”。
它将当前操作作为(,[],'a')放回到action_tuples
列表中
它尝试构造一个新的short可选,例如'-'+explicit_arg[0]
,其余的放回explicit_arg[1:]
。
通常,此步骤将-cdefoo
处理为'-c','-d','-e = foo'。
这里有些细节我不太了解。但是,如果这在99.999%的情况下都有效,并且仅在三重病理情况下失败,我犹豫不决要更改任何内容。引入更多错误或向后不兼容的机会实在太大了。没有专职开发人员照顾他argparse
。
答案 1 :(得分:1)
我认为 explicit_arg 一词混合了两种不同的东西:由'='引入的参数(我称其为等于大小写)和连接到短选项的struggnt (我称它为“便签盒”。)第一种情况允许使用long和short选项(--foo = bar或-f = bar),而后者仅在使用short选项时有效(仅-L / usr / lib)。而且这里还有另外一件事:如您所示,空的explicit_arg不是100%错误。当前导短选项不带参数时,代码尝试重新解释explicit_arg的含义,但不幸的是,第一种情况(相等情况)通过渲染空的explicit_arg来干扰其工作。因此,我故意通过了"-a="
而不是"-afoo"
。使用大写小写,您不能渲染空白的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
时,才进行重新解释以分析更短的选项。这应该消除我在这里发现的两个错误。