繁体   English   中英

传递“-a =”的Python argparse IndexError

[英]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_argequal_arg 只有在它是sticky_arg时才重新解释以解析更短的选项。 这应该消除我在这里发现的两个错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM