禁用argparse和optparse的唯一前缀匹配功能

26

当我使用Python的argparse或optparse命令行参数解析器时,任何参数的唯一前缀都被视为有效,例如:

$ ./buildall.py --help
usage: buildall.py [-h] [-f]

Build all repositories

optional arguments:
  -h, --help   show this help message and exit
  -f, --force  Build dirty repositories

使用--help--hel--he来获取帮助选项,使用--forc--fo来使用强制选项。

是否可以关闭此行为?我希望在参数不完整时获得错误消息。

3个回答

25
Python 3.5版本才添加了禁用缩写长选项的功能。根据 argparse文档 的说明:

parse_args() 方法默认情况下允许将长选项缩写为前缀,如果缩写不含糊(前缀匹配唯一一个选项)...可以通过将allow_abbrev设置为False来禁用此功能。

因此,如果您使用的是Python 3.5,可以使用allow_abbrev=False创建解析器:

parser = argparse.ArgumentParser(..., allow_abbrev=False)

如果您使用的是optparse或3.5之前的argparse版本,那么您只能使用缩写选项。


很好,你知道optparse的模拟选项吗? - Simon Warta
@SimonWarta:不,它被弃用是有原因的。 - user2357112
7
请注意,allow_abbrev 在 Python 3.5 之前的版本中不可用。 - chepner
@chepner:哦,该死,我没看到那个。看起来在3.5之前没有任何方法可以获得这种行为。真糟糕。 - user2357112
1
你可以从最新的Python发布中获取argparse.py文件,并将其放置在自己的目录中(这样它就具有加载优先级)。该模块是自包含的,因此它是一个即插即用的替代品。我所知道的唯一的Py2/3不兼容性是HelpFormatter代码中的yield from get_subactions()行。 - hpaulj
optparse仍然存在,并且将在很长一段时间内存在,但不要期望有任何变化。argparse正在进行更改,但速度非常缓慢。开发人员非常谨慎地处理向后兼容性问题,因此3.5只得到了几个argparse补丁。 - hpaulj

5

对于那些因为某些原因仍然停留在Python2.7的人来说,这是一种最小化改变的方法,可以在本地禁用前缀匹配:

class SaneArgumentParser(_argparse.ArgumentParser):
  """Disables prefix matching in ArgumentParser."""
  def _get_option_tuples(self, option_string):
    """Prevent argument parsing from looking for prefix matches."""
    return []

现在不再使用argparse.ArgumentParser,而是使用SaneArgumentParser。与chepner的答案不同,这不需要对argparse模块进行任何修改。这也是一个更小的改变。希望其他陷入Python过去的人会发现这很有用。


3
更简单的方法是直接修改解析器实例:parser._get_option_tuples = lambda option: [] - Nikita Nemkin
我对这个答案进行了负评,因为它破坏了短选项处理...然后发现当前的allow_abbrev实现以同样的方式破坏了短选项处理,并且有几个相关的错误报告在跟踪器上搁置着,指出它已经损坏了。 - user2357112

3

在Python 3.5之前,您必须对一个未记录的ArgumentParser方法进行猴子补丁。不要真正使用它;它未经测试,可能无法与所有版本(或任何版本)的Python一起使用。仅供娱乐目的。

import argparse

# This is a copy from argparse.py, with a single change
def _get_option_tuples(self, option_string):
    result = []

    # option strings starting with two prefix characters are only
    # split at the '='
    chars = self.prefix_chars
    if option_string[0] in chars and option_string[1] in chars:
        if '=' in option_string:
            option_prefix, explicit_arg = option_string.split('=', 1)
        else:
            option_prefix = option_string
            explicit_arg = None
        for option_string in self._option_string_actions:
            # === This is the change ===
            # if option_string.startswith(option_prefix):
            if option_string == option_prefix:
                action = self._option_string_actions[option_string]
                tup = action, option_string, explicit_arg
                result.append(tup)

    # single character options can be concatenated with their arguments
    # but multiple character options always have to have their argument
    # separate
    elif option_string[0] in chars and option_string[1] not in chars:
        option_prefix = option_string
        explicit_arg = None
        short_option_prefix = option_string[:2]
        short_explicit_arg = option_string[2:]

        for option_string in self._option_string_actions:
            if option_string == short_option_prefix:
                action = self._option_string_actions[option_string]
                tup = action, option_string, short_explicit_arg
                result.append(tup)
            elif option_string.startswith(option_prefix):
                action = self._option_string_actions[option_string]
                tup = action, option_string, explicit_arg
                result.append(tup)

    # shouldn't ever get here
    else:
        self.error(_('unexpected option string: %s') % option_string)

    # return the collected option tuples
    return result

argparse.ArgumentParser._get_option_tuples = _get_option_tuples
p = argparse.ArgumentParser()
p.add_argument("--foo")
print p.parse_args("--f 5".split())

改动不是很大——如果你愿意修改argparse.py的核心代码。如果你想要这种行为可切换,你需要进行更多的修改,以传递某种开关参数。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接