Сделайте argparse относиться к тире и подчеркивать одинаково - PullRequest
0 голосов
/ 28 ноября 2018

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 в качестве допустимых способов определения этого необязательного аргумента?

Ответы [ 2 ]

0 голосов
/ 28 ноября 2018
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 с определением optionalmetavar дает вам дополнительный контроль над дисплеем help.


Это parser._get_optional_kwargs, который выполняет замену '-':

    if dest is None:
        ....
        dest = dest.replace('-', '_')
0 голосов
/ 28 ноября 2018

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 здесь ).Использование этого подкласса должно сделать сопоставление необязательных аргументов инвариантным к использованию тире - или подчеркиваний _.

...