Розбір булевих значень з argparse


612

Я б хотів використовувати 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?


40
Ось однолінійне тлумачення відповіді @ mgilsonparser.add_argument('--feature', dest='feature', default=False, action='store_true') . Це рішення гарантує, що ви завжди отримаєте boolтип зі значенням Trueабо False. (Це рішення має обмеження. Ваша опція повинна мати значення за замовчуванням.)
Тревор Бойд Сміт

7
Ось однолінійне тлумачення відповіді @ Максима parser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Якщо параметр використовується, це рішення забезпечить boolтип зі значенням Trueабо False. Якщо параметр не використовується, ви отримаєте None. ( distutils.util.strtobool(x)йдеться з іншого запитання про стакове переповнення )
Тревор Бойд Сміт

8
як щодо чогось подібногоparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Відповіді:


273

Ще одне рішення з використанням попередніх пропозицій, але з помилкою "правильного" розбору 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>

і все-таки використовувати значення за замовчуванням (специфічне для налаштувань користувача). Один (опосередковано) недолік цього підходу полягає в тому, що "наркотики" можуть спіймати позитивний аргумент - див. Це пов'язане питання та цей звіт про помилку 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:бачусним, якщо аргумент встановлено на False, він ніколи не перейде умову. Якщо це так , то , може бути , краще повернутися список з str2boolфункції і безлічі списку в якості constпараметра, як це [True], [False]. Виправте мене, якщо я помиляюся
NutCracker

887

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

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?

96
Я все ще думаю, що type=boolслід вийти з поля (врахуйте позиційні аргументи!). Навіть коли ви додатково вказуєте choices=[False,True], ви закінчуєтесь як "False", так і "True", що вважаються True (за рахунок передачі від рядка до bool?). Можливо, пов'язане питання
дельфін

41
Правильно, я просто думаю, що немає виправдання, щоб це не працювало так, як очікувалося. І це вкрай оманливо, оскільки немає жодних перевірок безпеки та повідомлень про помилки.
дельфін

69
@mgilson - Я вважаю оманливим те, що ви можете встановити type = bool, ви не отримаєте повідомлення про помилку, і все ж для рядкових аргументів "False" та "True" ви отримуєте True у своїй нібито бульній змінній (через те, як кастинг типу працює в python). Отже, або type = bool має бути явно непідтримуваним (випромінювати якесь попередження, помилку тощо), або він повинен працювати так, як це корисно та інтуїтивно очікується.
дельфін

14
@dolphin - відповідно, я не згоден. Я думаю, що поведінка саме така, яка повинна бути, і відповідає дзену пітона "Особливі випадки недостатньо спеціальні, щоб порушувати правила". Однак якщо ви відчуваєте це сильно з цього приводу, чому б не відобразити його в одному з різних списків розсилки python ? Там, можливо, ви маєте шанс переконати когось, хто має владу щось зробити з цього питання. Навіть якщо ви змогли переконати мене, вам вдасться лише переконати мене, і поведінка все одно не зміниться, оскільки я не дев :)
mgilson

15
Ми сперечаємось про те, що bool()повинна виконувати функція Python , або що аргумент повинен приймати 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')

5
@CharlieParker add_argumentвикликається за допомогою dest='feature'. set_defaultsназивається с feature=True. Розумієте?
fnkr

4
Ця відповідь або mgilson повинна була бути прийнятою відповіддю - навіть хоча ОР хотіла --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)
Алекс Че

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

5
Ця відповідь занижена, але чудова своєю простотою. Не намагайтеся встановити, required=Trueінакше ви завжди отримаєте справжній аргумент.
Garren S

1
Будь ласка , ніколи не використовувати оператор рівності на речі , як BOOL або nonetype. Ви повинні використовувати IS замість цього
webKnjaZ

2
Це краща відповідь, ніж прийнята, оскільки вона просто перевіряє наявність прапора для встановлення булевого значення, замість того, щоб вимагати надлишкові булеві рядки. (Йо дауг, я чув, як ти любиш булевих ... тож я дав тобі булевий з булевим, щоб встановити булевий!)
Сифон

4
Гм ... питання, як заявлено, схоже, що хоче використовувати "True" / "False" у самому командному рядку; однак з цим прикладом python3 test.py --do-something Falseне вдається error: unrecognized arguments: False, тож він насправді не відповідає на питання.
sdbbs

38

oneliner:

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функції приймають і класові цінності. Є безліч прикладів stackoverflow для визначення спеціальних значень для обох.


Якщо це не очевидно з попереднього обговорення, bool()це не означає "розбір рядка". З документації Python:

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

Контрастуйте це з

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


3
Або скористайтеся: parser.register ('type', 'bool', (lambda x: x.lower () in ("так", "true", "t", "1")))
Matyas

17

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

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

і використовуючи це для розбору рядка до булевого, як було запропоновано вище.


5
Якщо ви збираєтесь піти цим маршрутом, я можу запропонувати distutils.util.strtobool(v).
CivFan

1
У distutils.util.strtoboolповертає 1 або 0, а не фактичне логічне.
CMCDragonkai

14

Досить схожий спосіб - це використовувати:

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

і якщо ви встановите аргумент - особливість у вашій команді

 command --feature

аргумент буде істинним, якщо ви не встановили тип - особливості аргументів за замовчуванням завжди помилкові!


1
Чи є якийсь недолік цього методу, який подолають інші відповіді? Це, здається, є найпростішим, найкоротшим рішенням, яке досягає того, чого хотіла ОП (і в даному випадку я). Я це люблю.
Simon

2
Хоча це просто, але це не дає відповіді на питання. ОП хочу аргументувати, де можна вказати--feature False
Астаріул

12

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


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

Відмінно! Я йду з цією відповіддю. Я налаштував _str_to_bool(s)конвертувати s = s.lower()один раз, потім тестувати if s not in {'true', 'false', '1', '0'}, і нарешті return s in {'true', '1'}.
Джеррі101

6

Більш простим способом було б використання, як показано нижче.

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

5

Найпростіший. Це не гнучко, але я віддаю перевагу простоті.

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

EDIT: Якщо ви не довіряєте введенню, не використовуйте eval.


Це здається досить зручним. Я помітив, що ти є eval як тип. У мене виникло питання з цього приводу: як слід визначити eval, чи потрібен імпорт, щоб ним скористатися?
edesz

1
eval- це вбудована функція. docs.python.org/3/library/functions.html#eval Це може бути будь-яка одинарна функція, якою користуються інші, більш гнучкі підходи.
Рассел

Гей, це чудово. Дякую!
edesz

2
це мило, але досить ризиковано просто потрапляти в дику природу, де користувачі, які не знають про те, що зло є злом , просто копіюють і вставляють його в свої сценарії.
Арне

@Arne, хороший момент. Хоча, здається, що добровільному користувачеві було б досить важко випадково зробити щось згубне.
Рассел

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. Потрібно = True варіант може бути доданий , якщо ви завжди хочете, щоб користувач явно вказати вибір.



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

Зауважте, що значення True - y, так, t, true, on і 1; значення false - 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

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

-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.