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

617

Я хотел бы использовать 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?

SuperElectric
источник
40
Вот однострочная интерпретация ответа @ mgilson parser.add_argument('--feature', dest='feature', default=False, action='store_true') . Это решение гарантирует, что вы всегда получите boolтип со значением Trueили False. (У этого решения есть ограничение: ваш вариант должен иметь значение по умолчанию.)
Тревор Бойд Смит,
7
Вот однострочная интерпретация ответа @ Maxim parser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Когда опция используется, это решение обеспечит boolтип со значением Trueили False. Когда опция не используется, вы получите None. ( distutils.util.strtobool(x)из другого вопроса stackoverflow )
Тревор Бойд Смит,
8
как насчет чего-то вродеparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Ответы:

276

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

Максим
источник
4
nargs = '?' означает ноль или один аргумент. docs.python.org/3/library/argparse.html#nargs
Максим
1
Я люблю это, но мой эквивалент по умолчанию = NICE дает мне ошибку, поэтому я должен сделать что-то еще.
Майкл Мэтьюз
2
@MarcelloRomani str2bool - это не тип в смысле Python, это определенная выше функция, ее нужно где-то включить.
Максим
4
код str2bool(v)может быть заменен на bool(distutils.util.strtobool(v)). Источник: stackoverflow.com/a/18472142/2436175
Антонио
4
Возможно, стоит упомянуть, что таким способом вы не сможете проверить, установлен ли аргумент с помощью if args.nice:beacuse, если для аргумента установлено значение False, условие никогда не пройдет. Если это так , то , может быть , лучше вернуться список из str2boolфункции и множества списка в качестве constпараметра, как это [True], [False]. Поправь меня, если я ошибаюсь
NutCracker,
889

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

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
источник
96
Я все еще думаю, что type=boolдолжно работать из коробки (рассмотрим позиционные аргументы!). Даже если вы дополнительно укажете choices=[False,True], вы получите «False» и «True», считающиеся True (из-за преобразования типа string в bool?). Возможно связанный вопрос
дельфин
41
Да, я просто думаю, что нет никаких оснований для того, чтобы это не работало, как ожидалось. И это крайне вводит в заблуждение, поскольку нет ни проверок безопасности, ни сообщений об ошибках.
Дельфин
69
@mgilson - Что меня вводит в заблуждение, так это то, что вы можете установить type = bool, вы не получите сообщение об ошибке и, тем не менее, для строковых аргументов "False" и "True" вы получите True в предположительно логической переменной (из-за того, как литьё по типу работ в питоне) Поэтому либо тип = bool должен быть явно не поддерживаемым (выдавать какое-то предупреждение, ошибку и т. Д.), Либо он должен работать так, как это полезно и интуитивно ожидается.
Дельфин
14
@ dolphin - соответственно я не согласен. Я думаю, что поведение именно так и должно быть и согласуется с дзен питона: «Особые случаи не настолько особенные, чтобы нарушать правила». Однако, если вы чувствуете это сильно по этому поводу, почему бы не включить его в один из различных списков рассылки python ? Там у вас может быть шанс убедить кого-то, кто имеет право что- то сделать по этому вопросу. Даже если вы сможете убедить меня, вам удастся убедить меня, и поведение все равно не изменится, так как я не разработчик :)
mgilson
15
Мы спорим о том, что bool()должна делать функция Python или в чем должен принимать argparse type=fn? Все argparseпроверки это то, что fnможно вызвать. Ожидается, что он получит fnодин строковый аргумент и вернет значение. Поведение fn- это ответственность программиста, а не argparse's.
hpaulj
235

Я рекомендую ответ 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')
fnkr
источник
5
@CharlieParker add_argumentвызывается с dest='feature'. set_defaultsназывается с feature=True. Понимаю?
фнкр
4
Этот или ответ Мигилсона должен был быть принятым ответом - даже если ОП хотел --flag False, часть ответов СО должна касаться того, ЧТО они пытаются решить, а не только КАК. Там не должно быть абсолютно никаких оснований делать --flag Falseили , --other-flag Trueа затем использовать некоторые пользовательские парсер , чтобы преобразовать строку в булево .. action='store_true'и action='store_false'лучшие способы использовать булевы флаги
kevlarr
6
@cowlinator Почему ТАК в конечном итоге отвечает на «вопросы как указано»? Согласно его собственным правилам , ответ, ... can be “don’t do that”, but it should also include “try this instead”который (по крайней мере для меня) подразумевает ответы, должен идти глубже, когда это необходимо. Определенно бывают случаи, когда некоторые из нас, публикующих вопросы, могут извлечь пользу из руководства по лучшим / лучшим практикам и т. Д. Ответы «как указано» часто не делают этого. При этом ваше разочарование в связи с ответами, которые часто предполагают, что слишком много (или неправильно) полностью обоснованно.
Кевлар
2
Если кто-то хочет получить третье значение, если пользователь не указал явно функцию, ему нужно заменить последнюю строку наparser.set_defaults(feature=None)
Alex Che
2
Если мы хотим добавить help=запись для этого аргумента, куда он должен идти? В add_mutually_exclusive_group()вызове? В одном или обоих add_argument()звонках? Где-нибудь еще?
Кен Уильямс
57

Вот еще один вариант без дополнительных строк / с для установки значений по умолчанию. Для 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")
Schaki
источник
6
Этот ответ недооценен, но удивителен своей простотой. Не пытайтесь установить, required=Trueиначе вы всегда получите истинный аргумент.
Гаррен С
1
Пожалуйста, НИКОГДА не используйте оператор равенства для таких вещей, как bool или nontype. Вы должны использовать вместо IS
webKnjaZ
2
Это лучший ответ, чем принятый, потому что он просто проверяет наличие флага, чтобы установить логическое значение, вместо того, чтобы требовать избыточной логической строки. (Эй, черт возьми, я слышал, что вы как булевы ... так что я дал вам логическое значение с вашим логическим значением для установки вашего логического значения!)
Сифон
4
Хм ... вопрос, как было сказано, похоже, хочет использовать "True" / "False" в самой командной строке; Однако с этим примером python3 test.py --do-something Falseне удается error: unrecognized arguments: False, поэтому он не дает ответа на вопрос.
sdbbs
38

один лайнер:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Эвальдс Уртанс
источник
4
хорошо для поклонника oneliner, также это может быть немного улучшено:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui
35

Кажется, есть некоторая путаница относительно того, что 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 в целое число.

hpaulj
источник
3
Или используйте: parser.register ('type', 'bool', (lambda x: x.lower () in ("yes", "true", "t", "1")))
Matyas
17

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

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

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

susundberg
источник
5
Если вы собираетесь пойти по этому пути, могу я предложить distutils.util.strtobool(v).
CivFan
1
В distutils.util.strtoboolвозвращает 1 или 0, а не фактическое логическое.
CMCDragonkai
14

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

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

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

 command --feature

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

dl.meteo
источник
1
Есть ли какой-то недостаток в этом методе, который другие ответы преодолевают? Похоже, что это, безусловно, самое простое и лаконичное решение, которое достигает того, что хотел ОП (и в данном случае я). Я люблю это.
Саймон О'Хэнлон
2
Несмотря на простоту, он не отвечает на вопрос. ОП хочет аргумент, который вы можете указать--feature False
Astariul
12

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

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')
Пупси Джо Пит
источник
Отлично! Я иду с этим ответом. Я отлажен My _str_to_bool(s)преобразовать s = s.lower()один раз, то испытание if s not in {'true', 'false', '1', '0'}, и , наконец return s in {'true', '1'}.
Jerry101
6

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

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
arunkumarreddy
источник
5

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

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

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

Рассел
источник
Это кажется довольно удобным. Я заметил, что у вас есть Eval в качестве типа. У меня возник вопрос по этому поводу: как определить eval, или для его использования требуется импорт?
Эдес
1
evalэто встроенная функция. docs.python.org/3/library/functions.html#eval Это может быть любая унарная функция, которой могут воспользоваться другие, более гибкие подходы.
Рассел
Эй, это здорово. Спасибо!
Эдес
2
это мило, но довольно рискованно выпускать в дикую природу, где пользователи, которые не знают о том, чтобы быть злым , просто скопируют его в свои сценарии.
Арне
@ Арне, хорошая мысль. Хотя кажется, что для пользователя с благими намерениями было бы довольно трудно случайно сделать что-то пагубное.
Рассел
3

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

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

gerardw
источник
2

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

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

ENSURE = config.ensure is None
Андреас Мартенс
источник
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)
Роберт Т. МакГиббон
источник
1

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

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

Обратите внимание, что Истинными значениями являются y, да, t, true, on и 1; ложными значениями являются n, no, f, false, off и 0. Поднимает ValueError, если val - что-то еще.

Акаш Десарда
источник
0

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

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

Вывод будет «Ложь» после вызова с терминала:

python myscript.py 0
FEMengineer
источник
-1

Похоже на @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")
Youngjae
источник
-1

В качестве улучшения ответа @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
Коста Хуан
источник