用于传递“ -a =“

时间:2019-02-13 04:38:05

标签: python argparse

我正在研究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!           

这是一个旧错误吗?我应该如何报告此错误?

2 个答案:

答案 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_argequal_arg。仅当重新sticky_arg时,才进行重新解释以分析更短的选项。这应该消除我在这里发现的两个错误。