使 argparse 同等对待破折号和下划线

问题描述 投票:0回答:3

argparse
将可选参数中的破折号替换为下划线以确定其目的地:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--use-unicorns', action='store_true')
args = parser.parse_args(['--use-unicorns'])
print(args)  # returns: Namespace(use_unicorns=True)

但是用户必须记住该选项是

--use-unicorns
还是
--use_unicorns
;使用错误的变体会引发错误。

这可能会导致一些挫败感,因为代码中的变量

args.use_unicorns
并没有明确定义了哪个变体。

如何使

argparse
接受
--use-unicorns
--use_unicorns
作为定义此可选参数的有效方法?

python argparse
3个回答
12
投票

parser.add_argument
接受多个标志作为参数(链接到文档)。让解析器接受这两种变体的一种简单方法是将参数声明为

parser.add_argument('--use-unicorns', '--use_unicorns', action='store_true')

然而,这两个选项都会显示在帮助中,而且它不是很优雅,因为它迫使人们手动编写变体。

另一种方法是子类

argparse.ArgumentParser
以使匹配不变,用下划线替换破折号。这需要一点点摆弄,因为
argparse_ActionsContainer._parse_optional
argparse_ActionsContainer._get_option_tuples
都必须修改才能处理这种匹配和缩写,例如
--use_unic

我最终得到了以下子类方法,其中缩写的匹配从

_parse_optional
委托给
_get_option_tuples

from gettext import gettext as _
import argparse


class ArgumentParser(argparse.ArgumentParser):

    def _parse_optional(self, arg_string):
        # if it's an empty string, it was meant to be a positional
        if not arg_string:
            return None

        # if it doesn't start with a prefix, it was meant to be positional
        if not arg_string[0] in self.prefix_chars:
            return None

        # if it's just a single character, it was meant to be positional
        if len(arg_string) == 1:
            return None

        option_tuples = self._get_option_tuples(arg_string)

        # if multiple actions match, the option string was ambiguous
        if len(option_tuples) > 1:
            options = ', '.join([option_string
                for action, option_string, explicit_arg in option_tuples])
            args = {'option': arg_string, 'matches': options}
            msg = _('ambiguous option: %(option)s could match %(matches)s')
            self.error(msg % args)

        # if exactly one action matched, this segmentation is good,
        # so return the parsed action
        elif len(option_tuples) == 1:
            option_tuple, = option_tuples
            return option_tuple

        # if it was not found as an option, but it looks like a negative
        # number, it was meant to be positional
        # unless there are negative-number-like options
        if self._negative_number_matcher.match(arg_string):
            if not self._has_negative_number_optionals:
                return None

        # if it contains a space, it was meant to be a positional
        if ' ' in arg_string:
            return None

        # it was meant to be an optional but there is no such option
        # in this parser (though it might be a valid option in a subparser)
        return None, arg_string, None

    def _get_option_tuples(self, option_string):
        result = []

        if '=' in option_string:
            option_prefix, explicit_arg = option_string.split('=', 1)
        else:
            option_prefix = option_string
            explicit_arg = None
        if option_prefix in self._option_string_actions:
            action = self._option_string_actions[option_prefix]
            tup = action, option_prefix, explicit_arg
            result.append(tup)
        else:  # imperfect match
            chars = self.prefix_chars
            if option_string[0] in chars and option_string[1] not in chars:
                # short option: if single character, can be concatenated with arguments
                short_option_prefix = option_string[:2]
                short_explicit_arg = option_string[2:]
                if short_option_prefix in self._option_string_actions:
                    action = self._option_string_actions[short_option_prefix]
                    tup = action, short_option_prefix, short_explicit_arg
                    result.append(tup)

            underscored = {k.replace('-', '_'): k for k in self._option_string_actions}
            option_prefix = option_prefix.replace('-', '_')
            if option_prefix in underscored:
                action = self._option_string_actions[underscored[option_prefix]]
                tup = action, underscored[option_prefix], explicit_arg
                result.append(tup)
            elif self.allow_abbrev:
                    for option_string in underscored:
                        if option_string.startswith(option_prefix):
                            action = self._option_string_actions[underscored[option_string]]
                            tup = action, underscored[option_string], explicit_arg
                            result.append(tup)

        # return the collected option tuples
        return result

其中很多代码直接源自

argparse
中的相应方法(来自此处的 CPython 实现)。使用此子类应该使可选参数的匹配与使用破折号
-
或下划线
_
保持不变。


7
投票
parser.add_argument('--use-unicorns', action='store_true')
args = parser.parse_args(['--use-unicorns'])
print(args)  # returns: Namespace(use_unicorns=True)

argparse
将 '-' 转换为 '_',因为在标志中使用 '-' 是公认的 POSIX 实践。但
args.use-unicones
是 Python 不可接受的。换句话说,它进行翻译,因此
dest
将是有效的 Python 变量或属性名称。

请注意,

argparse
不与
positionals
一起执行此翻译。在这种情况下,程序员可以完全控制
dest
参数,并且可以选择任何方便的内容。由于
argparse
在访问
getattr
时仅使用
setattr
Namespace
,因此对有效
dest
的约束很小。

有两个用户。这是你,程序员,还有你的最终用户。对你来说方便的东西可能对其他人来说并不是最佳的。

您还可以通过定义

dest
来指定
optional
metavar
可让您进一步控制
help
显示。


执行“-”替换的是

parser._get_optional_kwargs

    if dest is None:
        ....
        dest = dest.replace('-', '_')

0
投票

另一种选择是以声明方式定义参数:

import argparse

def parse_args(argv):
    arguments = [
        {"args": ["--use_unicorns"], "action": "store_true"},
        {"args": ["--use_rainbows"], "action": "store_true"},
        # ...
    ]
    parser = argparse.ArgumentParser()
    for argument in arguments:
        h_args = [x.replace("_", "-") for x in argument["args"]]
        argument["args"].extend(x for x in h_args if x not in argument["args"])
        kwargs = {k: v for k, v in argument.items() if k != "args"}
        parser.add_argument(*argument["args"], **kwargs)
    return parser.parse_args(argv)

print(parse_args(["--use-unicorns"]))
© www.soinside.com 2019 - 2024. All rights reserved.