从ArgumentParser获取允许参数的正确方法

时间:2016-10-12 10:26:59

标签: python argparse

问题:从现有argparse.ArgumentParser对象访问可能参数的预期/官方方式是什么?

示例:让我们假设以下背景:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo', '-f', type=str)

在这里,我想获得以下允许参数列表:

['-h', '--foo', '--help', '-f']

我找到了以下解决方法,为我提供了诀窍

parser._option_string_actions.keys()

但我对此并不满意,因为它涉及访问未正式记录的_成员。什么是这项任务的正确选择?

4 个答案:

答案 0 :(得分:3)

我认为没有“更好”的方式来实现你想要的目标。

如果您确实不想使用_option_string_actions属性,则可以处理parser.format_usage()以检索选项,但是这样做,您将只获得短选项名称。

如果您想要短选项和长选项名称,则可以处理parser.format_help()

此过程可以使用非常简单的正则表达式完成:-+\w+

import re

OPTION_RE = re.compile(r"-+\w+")
PARSER_HELP = """usage: test_args_2.py [-h] [--foo FOO] [--bar BAR]

optional arguments:
  -h, --help         show this help message and exit
  --foo FOO, -f FOO  a random options
  --bar BAR, -b BAR  a more random option
"""

options = set(OPTION_RE.findall(PARSER_HELP))

print(options)
# set(['-f', '-b', '--bar', '-h', '--help', '--foo'])

或者你可以先创建一个包含参数解析器配置的字典,然后从中构建argmuent解析器。这样的dictionnary可以将选项名称作为键,将选项配置作为值。这样做,您可以通过使用itertools.chain展平的字典键访问选项列表:

import argparse
import itertools

parser_config = {
    ('--foo', '-f'): {"help": "a random options", "type": str},
    ('--bar', '-b'): {"help": "a more random option", "type": int, "default": 0}
}

parser = argparse.ArgumentParser()
for option, config in parser_config.items():
    parser.add_argument(*option, **config)

print(parser.format_help())
# usage: test_args_2.py [-h] [--foo FOO] [--bar BAR]
# 
# optional arguments:
#   -h, --help         show this help message and exit
#   --foo FOO, -f FOO  a random options
#   --bar BAR, -b BAR  a more random option

print(list(itertools.chain(*parser_config.keys())))
# ['--foo', '-f', '--bar', '-b']

如果我不愿意使用_option_string_actions,那么最后一种方式就是我会做的。

答案 1 :(得分:1)

这开始是一个笑话答案,但我从那时起就学到了一些东西 - 所以我发布了它。

假设,我们知道允许的选项的最大长度。在这种情况下,这是一个很好的答案:

from itertools import combinations

def parsable(option):
    try:
        return len(parser.parse_known_args(option.split())[1]) != 2
    except:
        return False

def test(tester, option):
    return any([tester(str(option) + ' ' + str(v)) for v in ['0', '0.0']])

def allowed_options(parser, max_len=3, min_len=1):
    acceptable = []
    for l in range(min_len, max_len + 1):
        for option in combinations([c for c in [chr(i) for i in range(33, 127)] if c != '-'], l):
            option = ''.join(option)
            acceptable += [p + option for p in ['-', '--'] if test(parsable, p + option)]
    return acceptable

当然这非常迂腐,因为这个问题并不需要任何特定的运行时间。所以我在这里忽略它。我也忽略了上述版本产生了一堆混乱的输出,因为one can get rid of it easily

但更重要的是,这种方法检测到以下有趣的argparse"功能":

  • 在OP示例中,argparse也允许--fo。这必须是一个错误。
  • 但是,再一次,在OP示例中,argparse也允许-fo(即将foo设置为o,没有空格或任何内容。这是有记录和有意的,但我不知道。

因此,正确的解决方案会更长一点,看起来像这样(只有parsable更改,我将省略其他方法):

def parsable(option):
    try:
        default = vars(parser.parse_known_args(['--' + '0' * 200])[0])
        parsed, remaining = parser.parse_known_args(option.split())
        if len(remaining)  == 2:
            return False
        parsed = vars(parsed)
        for k in parsed.keys():
            try:
                if k in default and default[k] != parsed[k] and float(parsed[k]) != 0.0:
                    return False  # Filter '-fx' cases where '-f' is the argument and 'x' the value.
            except:
                return False
        return True
    except:
        return False

摘要:除了所有限制(运行时和固定的最大选项长度)之外,这是唯一正确尊重真实parser行为的答案 - 无论多么糟糕。所以,在这里,一个绝对没用的完美答案。

答案 2 :(得分:0)

我必须同意Tryph的回答。

不漂亮,但您可以从parser.format_help()检索它们:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--foo', '-f', type=str)
goal = parser._option_string_actions.keys()

def get_allowed_arguments(parser):
    lines = parser.format_help().split('\n')
    line_index = 0
    number_of_lines = len(lines)
    found_optional_arguments = False
    # skip the first lines until the section 'optional arguments'
    while line_index < number_of_lines:
        if lines[line_index] == 'optional arguments:':
            found_optional_arguments = True
            line_index += 1
            break
        line_index += 1
    result_list = []
    if found_optional_arguments:
        while line_index < number_of_lines:
            arg_list = get_arguments_from_line(lines[line_index])
            if len(arg_list) == 0:
                break
            result_list += arg_list
            line_index += 1
    return result_list

def get_arguments_from_line(line):
    if line[:2] != '  ':
        return []
    arg_list = []
    i = 2
    N = len(line)
    inside_arg = False
    arg_start = 2
    while i < N:
        if line[i] == '-' and not inside_arg:
            arg_start = i
            inside_arg = True
        elif line[i] in [',',' '] and inside_arg:
            arg_list.append(line[arg_start:i+1])
            inside_arg = False
        i += 1
    return arg_list

answer = get_allowed_arguments(parser)

可能有一个正则表达式替代上面的混乱......

答案 3 :(得分:0)

首先关于argparse文档的说明 - 它基本上是一个如何使用的文档,而不是正式的API。 argparse所做的标准是代码本身,单元测试(test/test_argparse.py),以及对后向兼容性的瘫痪问题。

没有'官方'方式访问allowed arguments,因为用户通常不需要知道(除了阅读help/usage)。

但是让我用迭代会话中的简单解析器来说明:

In [247]: parser=argparse.ArgumentParser()
In [248]: a = parser.add_argument('pos')
In [249]: b = parser.add_argument('-f','--foo')

add_argument返回它创建的Action对象。这没有记录,但对任何以交互方式创建解析器的人来说都很明显。

parser对象有repr方法,显示主要参数。但它有更多属性,您可以在Ipython中看到vars(parser)parser.<tab>

In [250]: parser
Out[250]: ArgumentParser(prog='ipython3', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)

行动也有repr; Action子类由action参数确定。

In [251]: a
Out[251]: _StoreAction(option_strings=[], dest='pos', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [252]: b
Out[252]: _StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)

vars(a)等可用于查看所有属性。

parser属性为_actions,是所有已定义操作的列表。这是所有解析的基础。请注意,它包含自动创建的help操作。看看option_strings;确定Action是位置还是可选。

In [253]: parser._actions
Out[253]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None),
 _StoreAction(option_strings=[], dest='pos',....),
 _StoreAction(option_strings=['-f', '--foo'], dest='foo', ...)]

_option_string_actions是一个字典,从option_strings映射到Actions(_actions中出现的相同对象)。这些Action对象的引用会出现在argparse代码中的所有位置。

In [255]: parser._option_string_actions
Out[255]: 
{'--foo': _StoreAction(option_strings=['-f', '--foo'],....),
 '--help': _HelpAction(option_strings=['-h', '--help'],...),
 '-f': _StoreAction(option_strings=['-f', '--foo'], dest='foo',...),
 '-h': _HelpAction(option_strings=['-h', '--help'], ....)}

In [256]: list(parser._option_string_actions.keys())
Out[256]: ['-f', '--help', '-h', '--foo']

请注意,每个-字符串都有一个键,长或短;但pos没有任何内容,位置有一个空的option_strings参数。

如果您希望使用该键列表,请使用它,而不必担心_。它没有'公共'别名。

我能理解解析help以发现同样的事情;但是要避免使用“私有”属性需要做很多工作。如果您担心未记录的未记录属性被更改,您还应该担心要更改的帮助格式。这也不是文档的一部分。

help布局由parser.format_help控制。 usage是根据self._actions中的信息创建的。

中的信息帮助热线
    for action_group in self._action_groups:
        formatter.add_arguments(action_group._group_actions)

(你不想进入action groups吗?)。

另一种获取option_strings的方法 - 从_actions收集它们:

In [258]: [a.option_strings for a in parser._actions]
Out[258]: [['-h', '--help'], [], ['-f', '--foo']]

===================

稍微深入研究代码细节:

parser.add_argument创建一个Action,然后将其传递给parser._add_action。这是填充.actionsaction.option_strings的方法。

    self._actions.append(action)
    for option_string in action.option_strings:
        self._option_string_actions[option_string] = action