Разбор логических значений с помощью argparse

Я хотел бы использовать argparse для разбора логических аргументов командной строки, написанных как "--foo True" или "--foo False". Например:

my_program --my_boolean_flag False

Однако следующий тестовый код не делает то, что я хотел бы:

import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
parsed_args = parser.parse(cmd_line)

К сожалению, parsed_args.my_bool оценивается как True. Это имеет место, даже если я изменяю cmd_line как ["--my_bool", ""], что удивительно, так как bool("") evalutates to False.

Как я могу получить argparse для синтаксического анализа "False", "F", а их варианты в нижнем регистре - False?

Ответ 1

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

def str2bool(v):
    if isinstance(v, bool):
       return v
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    elif v.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError('Boolean value expected.')

Это очень полезно для переключения со значениями по умолчанию; например

parser.add_argument("--nice", type=str2bool, nargs='?',
                        const=True, default=False,
                        help="Activate nice mode.")

позволяет мне использовать:

script --nice
script --nice <bool>

и по-прежнему использовать значение по умолчанию (специфично для пользовательских настроек). Один (косвенно связанный) недостаток этого подхода заключается в том, что "nargs" может поймать позиционный аргумент - см. Этот связанный вопрос и этот отчет об ошибке argparse.

Ответ 2

Я думаю, что более канонический способ сделать это через:

command --feature

и

command --no-feature

argparse поддерживает эту версию:

parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Конечно, если вы действительно хотите версию --arg <True|False>, вы можете передать ast.literal_eval как "тип" или определенную пользователем функцию...

def t_or_f(arg):
    ua = str(arg).upper()
    if 'TRUE'.startswith(ua):
       return True
    elif 'FALSE'.startswith(ua):
       return False
    else:
       pass  #error condition maybe?

Ответ 3

Я рекомендую ответ mgilson, но с взаимоисключающей группой
так что вы не можете использовать --feature и --no-feature одновременно.

command --feature

а также

command --no-feature

но нет

command --feature --no-feature

Автор сценария:

feature_parser = parser.add_mutually_exclusive_group(required=False)
feature_parser.add_argument('--feature', dest='feature', action='store_true')
feature_parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Затем вы можете использовать этот помощник, если вы собираетесь установить многие из них:

def add_bool_arg(parser, name, default=False):
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--' + name, dest=name, action='store_true')
    group.add_argument('--no-' + name, dest=name, action='store_false')
    parser.set_defaults(**{name:default})

add_bool_arg(parser, 'useful-feature')
add_bool_arg(parser, 'even-more-useful-feature')

Ответ 4

Кажется, есть некоторая путаница в отношении того, что может означать type=bool и type='bool'. Если один (или оба) означает "запустить функцию bool() или" вернуть логическое значение "? Поскольку он type='bool' ничего не значит. add_argument дает ошибку 'bool' is not callable, как если бы вы использовали type='foobar' или type='int'.

Но argparse имеет реестр, который позволяет вам определять ключевые слова, подобные этому. Он в основном используется для action, например. `Действие = 'store_true'. Вы можете увидеть зарегистрированные ключевые слова с помощью:

parser._registries

который отображает словарь

{'action': {None: argparse._StoreAction,
  'append': argparse._AppendAction,
  'append_const': argparse._AppendConstAction,
...
 'type': {None: <function argparse.identity>}}

Существует множество определенных действий, но только один тип, по умолчанию, argparse.identity.

Этот код определяет ключевое слово 'bool':

def str2bool(v):
  #susendberg function
  return v.lower() in ("yes", "true", "t", "1")
p = argparse.ArgumentParser()
p.register('type','bool',str2bool) # add type keyword to registries
p.add_argument('-b',type='bool')  # do not use 'type=bool'
# p.add_argument('-b',type=str2bool) # works just as well
p.parse_args('-b false'.split())
Namespace(b=False)

parser.register() не документирован, но также не скрыт. По большей части программисту не нужно знать об этом, потому что type и action принимают значения функций и классов. Существует множество примеров stackoverflow для определения пользовательских значений для обоих.


Если это не очевидно из предыдущего обсуждения, bool() не означает "разбор строки". Из документации Python:

bool (x): преобразовать значение в логическое значение, используя стандартную процедуру проверки истинности.

Контрастируйте это с помощью

int (x): Преобразование числа или строки x в целое число.

Ответ 5

Вот еще один вариант без дополнительных строк/с для установки значений по умолчанию. Для bool всегда присваивается значение, чтобы его можно было использовать в логических выражениях без предварительной проверки.

import argparse
parser = argparse.ArgumentParser(description="Parse bool")
parser.add_argument("--do-something", default=False, action="store_true" , help="Flag to do something")
args = parser.parse_args()

if args.do_something:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) + " is always a bool")

Ответ 6

Oneliner:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))

Ответ 7

Я искал одну и ту же проблему, а imho - это решение:

def str2bool(v):
  return v.lower() in ("yes", "true", "t", "1")

и используя это для синтаксического анализа строки, как было предложено выше.

Ответ 8

В дополнение к тому, что сказал @mgilson, следует отметить, что существует также метод ArgumentParser.add_mutually_exclusive_group(required=False), который сделал бы его тривиальным для обеспечения того, чтобы --flag и --no-flag не используются одновременно.

Ответ 9

Аналогичным образом можно использовать:

feature.add_argument('--feature',action='store_true')

и если вы установите аргумент --feature в своей команде

 command --feature

аргумент будет True, если вы не задаете тип --feature, аргументы default всегда False!

Ответ 10

Это работает для всего, что я ожидаю от него:

add_boolean_argument(parser, 'foo', default=True)
parser.parse_args([])                   # Whatever the default was
parser.parse_args(['--foo'])            # True
parser.parse_args(['--nofoo'])          # False
parser.parse_args(['--foo=true'])       # True
parser.parse_args(['--foo=false'])      # False
parser.parse_args(['--foo', '--nofoo']) # Error

Код:

def _str_to_bool(s):
    """Convert string to bool (in argparse context)."""
    if s.lower() not in ['true', 'false']:
        raise ValueError('Need bool; got %r' % s)
    return {'true': True, 'false': False}[s.lower()]

def add_boolean_argument(parser, name, default=False):                                                                                               
    """Add a boolean argument to an ArgumentParser instance."""
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        '--' + name, nargs='?', default=default, const=True, type=_str_to_bool)
    group.add_argument('--no' + name, dest=name, action='store_false')

Ответ 11

Более простой способ - использовать ниже.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])

Ответ 12

Я думаю, что самый канонический способ будет:

parser.add_argument('--ensure', nargs='*', default=None)

ENSURE = config.ensure is None

Ответ 13

class FlagAction(argparse.Action):
    # From http://bugs.python.org/issue8538

    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()
        for string in option_strings:
            assert re.match(r'--[A-z]+', string)
            suffix = string[2:]
            for positive_prefix in positive_prefixes:
                self.positive_strings.add(positive_prefix + suffix)
            for negative_prefix in negative_prefixes:
                self.negative_strings.add(negative_prefix + suffix)
        strings = list(self.positive_strings | self.negative_strings)
        super(FlagAction, self).__init__(option_strings=strings, dest=dest,
                                         nargs=0, const=None, default=default, type=bool, choices=None,
                                         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)

Ответ 14

Простейшим способом было бы использовать выбор:

parser = argparse.ArgumentParser()
parser.add_argument('--my-flag',choices=('True','False'))

args = parser.parse_args()
flag = args.my_flag == 'True'
print(flag)

Не передавая --my-flag, оценивается как False. Параметр required = True может быть добавлен, если вы всегда хотите, чтобы пользователь явно указал выбор.

Ответ 15

Быстро и просто, но только для аргументов 0 или 1:

parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)))
myargs=parser.parse_args()
print(myargs.mybool)

После вызова с терминала вывод будет "False":

python myscript.py 0