Разбор логических значений с помощью 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?

13 365

13 ответов:

еще одно решение с использованием предыдущих предложений, но с" правильной " ошибкой разбора от 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>

и по-прежнему использовать значение по умолчанию (в зависимости от настроек пользователя). Один (косвенно связанный) недостаток этого подхода заключается в том, что "Нарги" могут поймать позиционный аргумент-см. это связано вопрос и это сообщение об ошибке 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)

там, кажется, некоторая путаница относительно того, что type=bool и type='bool' может означать. Если один (или оба) означает ' запустить функцию bool(), или 'return a boolean'? Как он стоит 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 принимать значения функций и классов. Есть много примеров stackoverflow определения пользовательских значения для обоих.


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

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

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

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

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

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

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

oneliner:

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

вот еще один вариант без дополнительной строки / s для установки значений по умолчанию. 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")

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

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

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('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
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)

совершенно аналогичный способ заключается в использовании:

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

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

 command --feature

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

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

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

ENSURE = config.ensure is None