Python: аргументы командной строки --foo и --no-foo - PullRequest
0 голосов
/ 27 декабря 2018

Для анализа параметров логической командной строки с использованием встроенного в Python пакета argparse мне известен этот вопрос и несколько ответов: Анализ логических значений с помощью argparse .

Несколькоиз ответов (правильно, IMO) указывают, что наиболее распространенная и простая идиома для логических опций (с точки зрения вызывающей стороны) - это принятие опций --foo и --no-foo, что устанавливает некоторое значение в программе на True или False, соответственно.

Однако все ответы, которые я могу найти, на самом деле не выполняют задачу правильно, как мне кажется.Как правило, они не соответствуют одному из следующих пунктов:

  1. Можно установить подходящее значение по умолчанию (True, False или None).
  2. Текст справкиприведенный для program.py --help является правильным и полезным, включая показ значения по умолчанию.
  3. Любой из (мне все равно, какие из них, но иногда желательны):
    • Аргумент --foo может быть переопределено более поздним аргументом --no-foo и наоборот;
    • --foo и --no-foo являются несовместимыми и взаимоисключающими.

Что мне интересно, так это то, возможно ли вообще это сделать, используя argparse.

Вот самое близкое, что я пришел, основываясь на ответах @mgilson и @fnkr:

def add_bool_arg(parser, name, help_true, help_false, default=None, exclusive=True):
    if exclusive:
        group = parser.add_mutually_exclusive_group(required=False)
    else:
        group = parser
    group.add_argument('--' + name, dest=name, action='store_true', help=help_true)
    group.add_argument('--no-' + name, dest=name, action='store_false', help=help_false)
    parser.set_defaults(**{name: default})


parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
add_bool_arg(parser, 'foo', "Do foo", "Don't foo", exclusive=True)
add_bool_arg(parser, 'bar', "Do bar", "Don't bar", default=True, exclusive=False)

Это делает большинство вещей хорошо, но текст справки вводит в заблуждение:

usage: argtest.py [-h] [--foo | --no-foo] [--bar] [--no-bar]

optional arguments:
  -h, --help  show this help message and exit
  --foo       Do foo (default: None)
  --no-foo    Don't foo (default: None)
  --bar       Do bar (default: True)
  --no-bar    Don't bar (default: True)

Лучшим текстом справки будет что-то вроде этого:

usage: argtest.py [-h] [--foo | --no-foo] [--bar] [--no-bar]

optional arguments:
  -h, --help      show this help message and exit
  --foo --no-foo  Whether to foo (default: None)
  --bar --no-bar  Whether to bar (default: True)

Но я не вижуспособ сделать это, так как "- *" и "--no- *" всегда должны быть объявлены как отдельные аргументы (верно?).

В дополнение к suggeВ ответах на вопрос SO, упомянутый выше, я также попытался создать пользовательское действие, используя методы, показанные в этом другом вопросе SO: Python argparse настраиваемые действия с дополнительными аргументами, переданными .Они сразу перестают говорить, либо "error: argument --foo: expected one argument", либо (если я установил nargs=0) "ValueError: nargs for store actions must be > 0".Из-за поиска в источнике argparse это выглядит так, потому что действия, отличные от предопределенных store_const, store_true, append и т. Д., Должны использовать класс _StoreAction, который требует аргумент.

Есть ли другой способ сделать это?Если у кого-то есть комбинация идей, о которых я еще не подумал, пожалуйста, дайте мне знать!

(Кстати, я создаю этот новый вопрос, а не пытаюсь добавить его к первому вопросу выше, потому чтоПервоначальный вопрос, приведенный выше, на самом деле требовал метода для обработки аргументов --foo TRUE и --foo FALSE, который отличается и ИМО встречается реже.

1 Ответ

0 голосов
/ 27 декабря 2018

Один из ответов в связанном вопросе , в частности ответ Роберт Т. МакГиббон ​​, включает фрагмент кода из запроса на улучшение , который никогда не былпринят в стандартную argparse.Это работает довольно хорошо, хотя, если вы сбрасываете со счетов одно раздражение.Вот мое воспроизведение с несколькими небольшими изменениями в виде отдельного модуля с небольшим количеством добавленной строки pydoc и пример его использования:

import argparse
import re

class FlagAction(argparse.Action):
    """
    GNU style --foo/--no-foo flag action for argparse
    (via http://bugs.python.org/issue8538 and
    https://stackoverflow.com/a/26618391/1256452).

    This provides a GNU style flag action for argparse.  Use
    as, e.g., parser.add_argument('--foo', action=FlagAction).
    The destination will default to 'foo' and the default value
    if neither --foo or --no-foo are specified will be None
    (so that you can tell if one or the other was given).
    """
    def __init__(self, option_strings, dest, default=None,
                 required=False, help=None, metavar=None,
                 positive_prefixes=['--'], negative_prefixes=['--no-']):
        self.positive_strings = set()
        # self.negative_strings = set()
        # Order of strings is important: the first one is the only
        # one that will be shown in the short usage message!  (This
        # is an annoying little flaw.)
        strings = []
        for string in option_strings:
            assert re.match(r'--[a-z]+', string, re.IGNORECASE)
            suffix = string[2:]
            for positive_prefix in positive_prefixes:
                s = positive_prefix + suffix
                self.positive_strings.add(s)
                strings.append(s)
            for negative_prefix in negative_prefixes:
                s = negative_prefix + suffix
                # self.negative_strings.add(s)
                strings.append(s)
        super(FlagAction, self).__init__(option_strings=strings, dest=dest,
                                         nargs=0, default=default,
                                         required=required, help=help,
                                         metavar=metavar)

    def __call__(self, parser, namespace, values, option_string=None):
        if option_string in self.positive_strings:
            setattr(namespace, self.dest, True)
        else:
            setattr(namespace, self.dest, False)


if __name__ == '__main__':
    p = argparse.ArgumentParser()
    p.add_argument('-a', '--arg', help='example')
    p.add_argument('--foo', action=FlagAction, help='the boolean thing')
    args = p.parse_args()
    print(args)

(этот код работает в Python 2 и3 оба).

Вот что работает:

$ python flag_action.py -h
usage: flag_action.py [-h] [-a ARG] [--foo]

optional arguments:
  -h, --help         show this help message and exit
  -a ARG, --arg ARG  example
  --foo, --no-foo    the boolean thing

Обратите внимание, что в исходном сообщении usage не упоминается опция --no-foo.Нет простого способа исправить это, кроме как использовать групповой метод, который вам не нравится.

$ python flag_action.py -a something --foo
Namespace(arg='something', foo=True)
$ python flag_action.py --no-foo
Namespace(arg=None, foo=False)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...