Разбор логических значений с помощью 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("") оценивает False,

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

27 ответов

Решение

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

def str2bool(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=NICE,
                        help="Activate nice mode.")

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

script --nice
script --nice <bool>

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

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

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?

Я рекомендую ответ 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')

Вот еще один вариант без дополнительных строк / с для установки значений по умолчанию. Для 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 == True:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) + " is always a bool")

Один лайнер:

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

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

Но argparse действительно есть реестр, который позволяет вам определять ключевые слова, как это. В основном используется для actionНапример, `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's 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 принимать значения функций и классов. Существует множество примеров стекового потока для определения пользовательских значений для обоих.


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

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

Сравните это с

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

Самый простой и правильный способ -

from distutils import util
arser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x)))

Обратите внимание, что истинные значения: y, yes, t, true, on и 1; значениями false являются n, no, f, false, off и 0. Вызывает ошибку ValueError, если val принимает другое значение.

Весьма похожим способом является использование:

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

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

 command --feature

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

Я искал ту же проблему, и имхо красивое решение:

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

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

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

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')

Самый простой. Это не гибко, но я предпочитаю простоту.

  parser.add_argument('--boolean_flag',
                      help='This is a boolean flag.',
                      type=eval, 
                      choices=[True, False], 
                      default='True')

РЕДАКТИРОВАТЬ: если вы не доверяете вводу, не используйтеeval.

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

Это действительно устарело. Argparse теперь поддерживает логические аргументы (поиск BooleanOptionalAction).

Реализация выглядит так:

      import argparse

ap = argparse.ArgumentParser()

# List of args
ap.add_argument('--foo', default=True, type=bool, help='Some helpful text that is not bar. Default = True')

# Importable object
args = ap.parse_args()

Несмотря на то, что документация, по-видимому, подразумевает, что это работает только в Python 3.7+, на самом деле это работает с Python 2.7, если вы обременены устаревшим интерпретатором, как и я.

Еще одна вещь, которую стоит упомянуть: это заблокирует все записи, кроме True и False для аргумента через argparse.ArgumentTypeError. Вы можете создать собственный класс ошибок для этого, если хотите попытаться изменить это по какой-либо причине.

Более простым способом было бы использовать, как показано ниже.

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

После того, как я ранее следил за ответом на превосходство @akash-desarda /questions/41124414/razbor-logicheskih-znachenij-s-pomoschyu-argparse/55220293#55220293 , чтобы использовать через, позже, я решил использовать вместо этого напрямую.

      import argparse
from distutils import util
parser.add_argument('--feature', type=util.strtobool)

Да ты прав, возвращается int, а не. Но не вернет никакого другого значения, кроме 0 а также 1, и python преобразует их в bool ценить плавно и последовательно.

      >>> 0 == False
True
>>> 0 == True
False
>>> 1 == False
False
>>> 1 == True
True

При получении неправильного входного значения, например

      python yours.py --feature wrong_value

Argparse.Action с strtobool по сравнению с lambda выдаст более четкое / понятное сообщение об ошибке:

      yours.py: error: argument --feature: invalid strtobool value: 'wrong_value'

По сравнению с этим кодом,

      parser.add_argument('--feature', type=lambda x: bool(util.strtobool(x))

Это приведет к менее четкому сообщению об ошибке:

      yours.py: error: argument --feature: invalid <lambda> value: 'wrong_value'

На самом деле, очень легко. Никакие пакеты импортировать не нужно.

Помните, чтоtype=funcозначает возврат func(x), напримерtype=bool -> bool(x), так:

      parser.add_argument('--feature', default=False, type=lambda x: x == 'True')

Теперь, когда мы узнали этот принцип, вы также можете распространить его наtype=lambda x: x.lower() not in ['false', 'no', '0', 'None', ...].

Расширение ответа gerardw

Причина parser.add_argument("--my_bool", type=bool)не работает это bool("mystring")для любой непустой строки, поэтому bool("False")на самом деле True.

То, что вы хотите, это

my_program.py

      import argparse

parser = argparse.ArgumentParser(description="My parser")

parser.add_argument(
        "--my_bool",
        choices=["False", "True"],
        )

parsed_args = parser.parse_args()

my_bool = parsed_args.my_bool == "True"

print(my_bool)
      $ python my_program.py --my_bool False
False

$ python my_program.py --my_bool True
True

$ python my_program.py --my_bool true
usage: my_program.py [-h] [--my_bool {False,True}]
my_program.py: error: argument --my_bool: invalid choice: 'true' (choose from 'False', 'True')

Самый простой способ - использовать варианты:

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

Я нашел хороший способ сохранить значение параметра по умолчанию как False, и когда оно присутствует в аргументе командной строки, его значение должно быть истинным.

cmd, если вы хотите, чтобы аргумент был истинным:python main.py --csv

когда вы хотите, чтобы ваш аргумент был ложным:python main.py

      import argparse
from ast import parse
import sys
parser = argparse.ArgumentParser(description='')
parser.add_argument('--csv', action='store_true', default = False 
,help='read from csv')
args = parser.parse_args()

if args.csv:
    print('reading from csv')

В качестве улучшения ответа @Akash Desarda вы могли бы сделать

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument("--foo", 
    type=lambda x:bool(strtobool(x)),
    nargs='?', const=True, default=False)
args = parser.parse_args()
print(args.foo)

И поддерживает python test.py --foo

(base) [costa@costa-pc code]$ python test.py
False
(base) [costa@costa-pc code]$ python test.py --foo 
True
(base) [costa@costa-pc code]$ python test.py --foo True
True
(base) [costa@costa-pc code]$ python test.py --foo False
False

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

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

ENSURE = config.ensure is None

Быстро и легко, но только для аргументов 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
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)

Вы можете создать BoolAction, а затем использовать его

      class BoolAction(Action):
    def __init__(
            self,
            option_strings,
            dest,
            nargs=None,
            default: bool = False,
            **kwargs,
    ):
        if nargs is not None:
            raise ValueError('nargs not allowed')
        super().__init__(option_strings, dest, default=default, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        input_value = values.lower()
        b = input_value in ['true', 'yes', '1']
        if not b and input_value not in ['false', 'no', '0']:
            raise ValueError('Invalid boolean value "%s".)
        setattr(namespace, self.dest, b)

а затем установитьaction=BoolActionвparser.add_argument()

Просто сделайте следующее, вы можете сделать --test = True используя

имя файла python --test

parser.add_argument("--test" , default=False ,help="test ?", dest='test', action='store_true')

Преобразуйте значение:

      def __arg_to_bool__(arg):
    """__arg_to_bool__

        Convert string / int arg to bool
    :param arg: argument to be converted
    :type arg: str or int
    :return: converted arg
    :rtype: bool
    """
    str_true_values = (
        '1',
        'ENABLED',
        'ON',
        'TRUE',
        'YES',
    )
    str_false_values = (
        '0',
        'DISABLED',
        'OFF',
        'FALSE',
        'NO',
    )

    if isinstance(arg, str):
        arg = arg.upper()
        if arg in str_true_values:
            return True
        elif arg in str_false_values:
            return False

    if isinstance(arg, int):
        if arg == 1:
            return True
        elif arg == 0:
            return False

    if isinstance(arg, bool):
        return arg

    # if any other value not covered above, consider argument as False
    # or you could just raise and error
    return False

[...]


args = ap.parse_args()
my_arg = options.my_arg
my_arg = __arg_to_bool__(my_arg)

Подобно @Akash, но вот другой подход, который я использовал. Оно используетstr чем lambda потому что питон lambda всегда вызывает у меня чужие чувства.

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument("--my_bool", type=str, default="False")
args = parser.parse_args()

if bool(strtobool(args.my_bool)) is True:
    print("OK")
Другие вопросы по тегам