使用Argparse解析布尔值

我想使用artparse来解析写为“--foo True”或“--foo False”的布尔命令行参数。例如:

my_program --my_boolean_flag False

但是,以下测试代码并没有做我想做的事情:

import argparseparser = 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

我怎么能让artparse解析"False""F",和他们的小写变体False

724634 次浏览

我认为一个更规范的方法是通过:

command --feature

command --no-feature

argparse很好地支持这个版本:

python3.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 Trueelif 'FALSE'.startswith(ua):return Falseelse:pass  #error condition maybe?

我一直在寻找同样的问题,在我看来,漂亮的解决方案是:

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

并使用它将字符串解析为布尔值,如上所述。

对于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 functionreturn v.lower() in ("yes", "true", "t", "1")p = argparse.ArgumentParser()p.register('type','bool',str2bool) # add type keyword to registriesp.add_argument('-b',type='bool')  # do not use 'type=bool'# p.add_argument('-b',type=str2bool) # works just as wellp.parse_args('-b false'.split())Namespace(b=False)

parser.register()没有记录,但也没有隐藏。在大多数情况下,程序员不需要知道它,因为typeaction采用函数和类值。有很多为两者定义自定义值的stackoverflow示例。


如果从前面的讨论中看不出来,bool()并不意味着“解析字符串”。从Python留档:

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

将此与

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

除了@mgilson所说的之外,应该注意的是,还有一个ArgumentParser.add_mutually_exclusive_group(required=False)方法可以使强制执行--flag--no-flag不同时使用变得微不足道。

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

一个非常相似的方法是使用:

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

如果你在命令中设置了参数——特性

 command --feature

参数将为True,如果您不设置type--功能,参数默认值始终为False!

这适用于我期望的一切:

add_boolean_argument(parser, 'foo', default=True)parser.parse_args([])                   # Whatever the default wasparser.parse_args(['--foo'])            # Trueparser.parse_args(['--nofoo'])          # Falseparser.parse_args(['--foo=true'])       # Trueparser.parse_args(['--foo=false'])      # Falseparser.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')

这是另一个没有额外行/s来设置默认值的变体。布尔值总是被分配的,这样它就可以在逻辑语句中使用,而无需事先检查:

import argparseparser = 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.")

更简单的方法是使用如下。

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

我认为最典型的方法是:

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

使用前面的建议的另一个解决方案,但具有argparse中的“正确”解析错误:

def str2bool(v):if isinstance(v, bool):return vif v.lower() in ('yes', 'true', 't', 'y', '1'):return Trueelif v.lower() in ('no', 'false', 'f', 'n', '0'):return Falseelse:raise argparse.ArgumentTypeError('Boolean value expected.')

这对于使用默认值进行切换非常有用;例如

parser.add_argument("--nice", type=str2bool, nargs='?',const=True, default=False,help="Activate nice mode.")

允许我使用:

script --nicescript --nice <bool>

并且仍然使用默认值(特定于用户设置)。这种方法的一个(间接相关的)缺点是'nargs'可能会捕获位置参数——参见这个相关问题这篇文章bug报告

oneliner:

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

最简单的方法是使用选择

parser = argparse.ArgumentParser()parser.add_argument('--my-flag',choices=('True','False'))
args = parser.parse_args()flag = args.my_flag == 'True'print(flag)

不传递--my-标志的计算结果为False。如果您始终希望用户显式指定选择,可以添加需要=True选项。

快速简单,但仅适用于参数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

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

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。

最简单。它不灵活,但我更喜欢简单。

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

编辑:如果您不信任输入,请不要使用eval

与@Akash类似,但这是我使用的另一种方法。它使用str而不是lambda,因为pythonlambda总是给我一种异类的感觉。

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

作为对@Akash Desarda答案的改进,您可以这样做

import argparsefrom 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.pyFalse(base) [costa@costa-pc code]$ python test.py --fooTrue(base) [costa@costa-pc code]$ python test.py --foo TrueTrue(base) [costa@costa-pc code]$ python test.py --foo FalseFalse

只需执行以下操作,您就可以使用--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 Trueelif arg in str_false_values:return False
if isinstance(arg, int):if arg == 1:return Trueelif 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 errorreturn False
[...]

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

在之前遵循@akash-desarda的卓越答案https://stackoverflow.com/a/59579733/315112之后,通过lambda使用strtobool,后来,我决定直接使用strtobool

import argparsefrom distutils import utilparser.add_argument('--feature', type=util.strtobool)

是的,你是对的,strtobool返回的是int,而不是bool。但是strtobool不会返回除01之外的任何其他值,python会无缝一致地将它们转换为bool值。

>>> 0 == FalseTrue>>> 0 == TrueFalse>>> 1 == FalseFalse>>> 1 == TrueTrue

当接收到错误的输入值时,如

python yours.py --feature wrong_value

lambda相比,strtobool的artparse. Action将产生一个稍微清晰/可理解的错误消息:

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'

这实际上已经过时了。对于Python 3.7+,Argparse现在支持布尔参数(搜索BooleanOptionalAction)。

实现看起来像这样:

import argparse
ap = argparse.ArgumentParser()
# List of argsap.add_argument('--foo', default=True, type=bool, help='Some helpful text that is not bar. Default = True')
# Importable objectargs = ap.parse_args()

还有一件事要提:这将阻止除True和False之外的所有条目,如果您想出于任何原因尝试更改它,您可以为此创建一个自定义错误类。

扩展Gerardw的回答

parser.add_argument("--my_bool", type=bool)不起作用的原因是bool("mystring")对于任何非空字符串都是True,所以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 FalseFalse
$ python my_program.py --my_bool TrueTrue
$ python my_program.py --my_bool trueusage: my_program.py [-h] [--my_bool {False,True}]my_program.py: error: argument --my_bool: invalid choice: 'true' (choose from 'False', 'True')

我找到了将参数的默认值存储为False的好方法,当它出现在命令行参数中时,它的值应该为true。

cmd命令如果你想证明是真的:pythonmain.py--csv

如果你想要你的参数应该是false:Pythonmain.py

import argparsefrom ast import parseimport sysparser = 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')

你可以创建一个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()