我想使用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解析“假”,“F”,和他们的小写变体为假?
对于type=bool和type='bool'可能意味着什么,似乎存在一些混淆。一个(或两个)应该意味着'运行函数bool(),还是'返回布尔值'?type='bool'没有任何意义。Add_argument给出了一个'bool'不可调用的错误,与使用type='foobar'或type='int'时相同。
但argparse确实有注册表,允许你这样定义关键字。它主要用于动作,例如: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(),但也没有隐藏。在大多数情况下,程序员不需要知道它,因为类型和动作需要函数值和类值。在stackoverflow中有许多为两者定义自定义值的示例。
如果从前面的讨论中不明显,bool()并不意味着“解析字符串”。来自Python文档:
bool(x):使用标准真值测试程序将值转换为布尔值。
对比一下
int(x):将数字或字符串x转换为整数。
这适用于我所期望的一切:
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')
在之前跟随@akash-desarda的优秀回答https://stackoverflow.com/a/59579733/315112后,通过lambda使用strtobool,后来,我决定直接使用strtobool。
import argparse
from distutils import util
parser.add_argument('--feature', type=util.strtobool)
是的,你是对的,strtobool返回的是int型,而不是bool型。但是strtobool将不会返回除0和1之外的任何其他值,而python将无缝一致地将它们转换为bool值。
>>> 0 == False
True
>>> 0 == True
False
>>> 1 == False
False
>>> 1 == True
True
当接收到错误的输入值时
python yours.py --feature wrong_value
一个argparse。与lambda相比,使用strtoool的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'
扩展杰拉德的回答
原因解析器。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 False
False
$ python my_program.py --my_bool True
True
$ python my_program.py --my_bool true
usage: my_program.py [-h] [--my_bool {False,True}]
my_program.py: error: argument --my_bool: invalid choice: 'true' (choose from 'False', 'True')
我认为更规范的做法是:
command --feature
and
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作为“类型”,或者一个用户定义的函数…
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?