使用argparse解析布尔值

1077
我希望使用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

87
以下是对@mgilson的回答的简短解释:parser.add_argument('--feature', dest='feature', default=False, action='store_true')。这个解决方案将确保您始终获得一个值为TrueFalsebool类型。(这个解决方案有一个限制:您的选项必须具有默认值。) - Trevor Boyd Smith
28
@Maxim的回答的简短解释是:使用这个选项时,该解决方案将确保得到一个值为“True”或“False”的布尔类型。当不使用该选项时,将得到“None”。(distutils.util.strtobool(x)来源于另一个stackoverflow问题) - Trevor Boyd Smith
12
可以尝试类似这样的代码:parser.add_argument('--my_bool', action='store_true', default=False) - AruniRC
3
对于@TrevorBoydSmith的答案,请尝试使用import distutils.util而不是import disutils进行导入。请参见此答案 - Osama Dar
6
刚刚遇到了同样的问题。令人惊讶的是,argparse 模块过于庞大臃肿,而且它并没有以开箱即用的方式完成一些简单的任务。更糟糕的是,它做错了这些事情。 - Anatoly Alekseev
1
@AnatolyAlekseev,argparse的开发人员充分意识到一些用户试图将像“True”或“False”这样的字符串处理为布尔值,但已经拒绝重新定义基本的Python bool函数的提议。解析可以表示True/False的单词太具有语言特定性,最好由程序员自己处理(而不是硬编码)。简单地说,type参数是一个function - hpaulj
27个回答

1445

我认为更加规范的做法是通过:

command --feature

并且

command --no-feature

argparse 工具非常好地支持了这个版本:

Python 3.9+

parser.add_argument('--feature', action=argparse.BooleanOptionalAction)

Python < 3.9:

parser.add_argument('--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 作为 "type" 传递,或者使用用户定义的函数...
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?

194
我认为type=bool 应该直接可用(考虑位置参数!)。即使你额外指定choices=[False,True],你最终得到的是"False"和"True"都被视为True(由于从字符串到布尔类型的强制转换?)。可能相关问题 - dolphin
81
没错,我认为没有理由这个不能按预期工作。这非常具有误导性,因为没有安全检查或错误提示。 - dolphin
135
我认为具有误导性的是,你可以将变量类型设置为bool,却不会收到任何错误信息。然而,无论是“False”还是“True”字符串参数,你在所谓的布尔变量中都会得到True(这是由于Python中的类型转换方式)。因此,要么明确不支持type=bool(发出警告、错误等),要么使其按照一种有用且符合直觉的方式工作。 - dolphin
21
@dolphin -- 分别来说,我不同意。我认为这种行为恰好符合Python禅宗的理念:“特殊情况并不足以打破规则”。但是,如果你对此感到强烈不满,为什么不在各种Python邮件列表之一上提出呢?在那里,你可能有机会说服有权力采取措施解决此问题的人。即使你能说服我,你也只是成功地说服了我,这种行为仍然不会改变,因为我不是开发者:) - mgilson
24
我们讨论的是Python的bool()函数应该做什么,还是argparse在type=fn中应该接受什么? argparse只检查fn是否可调用。它期望fn接受一个字符串参数并返回一个值。fn的行为是程序员的责任,而不是argparse的责任。 - hpaulj
显示剩余36条评论

499

以下是使用之前建议的另一种解决方案,但使用了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错误报告


10
nargs='?' 表示零个或一个参数。 https://docs.python.org/zh-cn/3/library/argparse.html#nargs - Maxim
1
我喜欢这个,但是我的默认值等价于NICE时出现了错误,所以我必须需要做些其他的事情。 - Michael Mathews
2
@MarcelloRomani,str2bool不是Python中的类型,它是上面定义的函数,你需要在某个地方包含它。 - Maxim
22
str2bool(v)的代码可以替换为bool(distutils.util.strtobool(v))。来源:https://dev59.com/snRB5IYBdhLWcg3wHkPi#18472142 - Antonio
4
也许值得一提的是,使用这种方式无法通过if args.nice:语句检查参数是否被设置,因为如果参数被设置为False,则永远无法通过该条件。 如果是这样的话,也许更好的方法是从str2bool函数返回列表,并将该列表设为常量参数,例如[True][False]。如果我有误,请纠正我。 - NutCracker
显示剩余9条评论

377

如果您想同时允许--feature--no-feature(以最后一个为准)

这使用户可以使用--feature创建shell别名,并使用--no-feature覆盖它。

Python 3.9及以上版本

parser.add_argument('--feature', default=True, action=argparse.BooleanOptionalAction)

Python 3.8 及以下版本

我建议使用mgilson的答案:

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

如果您不想同时允许--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
明白。add_argument 被调用时,使用了 dest='feature' 参数。set_defaults 被调用时,使用了 feature=True 参数。 - fnkr
5
这个答案,无论是这个回答者mgilson的答案还是此回答都应该被接受 - 即使提问者想要 --flag False,Stack Overflow(以下简称 SO)的回答部分也应该涉及到他们尝试解决的问题,而不仅仅是如何解决。完全没有理由去做 --flag False--other-flag True,然后使用一些自定义解析器将字符串转换为布尔值。action='store_true'action='store_false' 是使用布尔标志的最佳方式。 - kevlarr
6
为什么Stack Overflow最终关注的是回答“按照提问的方式”?根据其自己的指南,一个回答“......可以是‘不要那样做’,但也应该包括‘请尝试这个替代方法’”,这(至少对我来说)意味着在适当的时候回答应该更深入。有时候我们发帖提问的人确实会受益于更好/最佳实践指导等方面的引导。只回答“按照提问的方式”通常并不能做到这一点。话虽如此,你对答案常常假设过多(或不正确)的沮丧完全是合理的。 - kevlarr
3
如果想要为用户未明确指定功能时设置第三个值,需要将最后一行替换为 parser.set_defaults(feature=None) - Alex Che
3
如果我们想为这个参数添加一个help=条目,应该放在哪里?是在add_mutually_exclusive_group()调用中?还是在一个或两个add_argument()调用中?还是其他地方? - Ken Williams
显示剩余6条评论

128

这里有另一种变化,不需要额外的行来设置默认值。布尔值始终被分配,因此可以在逻辑语句中使用而无需事先检查:

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(f"Check that args.do_something={args.do_something} is always a bool.")

11
这个答案被低估了,但它的简单性非常出色。不要尝试设置 required=True,否则你将始终获得一个 True 参数。 - Garren S
1
请勿在布尔或无类型等内容上使用相等操作符。您应该改用 IS - webknjaz -- Слава Україні
8
这个回答比被采纳的更好,因为它只是检查标记的存在以设置布尔值,而不需要冗余的布尔字符串。(嘿,骚狗,我听说你喜欢布尔值...所以我给了你一个布尔值和一个设置你的布尔值的布尔值!) - Siphon
9
这个问题似乎想在命令行中使用“True”/“False”,但是以这个例子来看,使用python3 test.py --do-something False会失败,并显示“error: unrecognized arguments: False”,因此并没有真正回答这个问题。 - sdbbs
一个微不足道的注释:默认值 None 的默认默认值在这里通常也可以正常工作。 - Gringo Suave
@sdbbs 只需执行以下操作 python3 test.py --do-something ---> 这是 True python3 test.py ----> 这是 False - Smaurya

91

一句话:

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

21
适合喜欢单行代码的人,不过可以稍加改进:type=lambda x: (str(x).lower() in ['true','1', 'yes']) - Tu Bui
9
另一个选项是使用标准的 distutils.utils.strtobool,例如 type=lambda x: bool(strtobool(str(x)))。真值包括 y、yes、t、true、on 和 1;假值包括 n、no、f、false、off 和 0。 - Jethro
2
小修正@Jethro的评论:应该是distutils.util.strtobool(没有s)。非常好用! - Daniel Jones
strtobool 在这种情况下有点烦人,因为它不会去掉空格。所以你需要使用 lambda x: strtobool(x.strip())。否则,如果在 bash 脚本中有换行符的话,你将会崩溃在像 "false\r" 这样的东西上。 - grofte

41
似乎有一些关于type=booltype='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()没有详细的文档,但也不是隐藏的。对于大多数程序员来说,他们并不需要了解它,因为typeaction都接受函数和类的值。有很多stackoverflow示例可以定义自定义值。


如果从先前的讨论中不明显,bool()并不意味着“解析字符串”。根据Python文档:

bool(x):使用标准的真值测试过程将值转换为布尔值。

与此相反的是

int(x): 将数字或字符串x转换为整数。


3
parser.register('type', 'bool', (lambda x: x.lower() in ("yes", "true", "t", "1"))) - Matyas

37

最简单和最正确的方法是:

from distutils.util import strtobool

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

请注意,True 值包括 y、yes、t、true、on 和 1;false 值包括 n、no、f、false、off 和 0。如果 val 的值不属于这些范围,就会引发 ValueError 的异常。

1
这应该排得更靠前! :-) - Eike P.

30
一个相似的方法是使用:

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

如果您在命令中设置了参数--feature

 command --feature

如果您不设置--feature类型,参数的默认值始终为False,则该论点将为True!


1
这种方法是否存在其他答案可以克服的缺点?这似乎是迄今为止最简单、最简洁的解决方案,能够满足原帖(以及在这种情况下的我)的需求。我喜欢它。 - Simon O'Hanlon
5
虽然简单,但它并没有回答问题。OP想要一个论点,您可以在其中指定“-特性 假”。 - Astariul

21
我正在寻找同样的问题,我认为比较好的解决方案是:
def str2bool(v):
  return v.lower() in ("yes", "true", "t", "1")

并按照上述建议将其用于解析字符串为布尔值。

8
如果您打算采用这种方式,我建议使用distutils.util.strtobool(v) - CivFan
2
distutils.util.strtobool 返回的是 1 或 0,而不是实际的布尔值。 - CMCDragonkai
如果你想从strtobool获取一个布尔值,你可以使用lambda x: bool(strtobool(x.strip())。我建议你至少扩展这个函数,使它只在v.lower() in ("no", "false", "f", "0")时返回False,否则返回错误。 - grofte

15

这对我期望的所有事情都有效:

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'} - Jerry101

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接