关于python:使用argparse分析布尔值

Parsing boolean values with argparse

我想使用argparse解析写为"-foo-true"或"-foo-false"的布尔命令行参数。例如:

1
my_program --my_boolean_flag False

但是,以下测试代码不符合我的要求:

1
2
3
4
5
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


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

1
command --feature

1
command --no-feature

argparse很好地支持这个版本:

1
2
3
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 版本,你可以通过ast.literal_eval作为"类型",或者用户定义的函数…

1
2
3
4
5
6
7
8
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?


我推荐米吉尔森的答案,但有一个相互排斥的小组所以你不能同时使用--feature--no-feature

1
command --feature

1
command --no-feature

但不是

1
command --feature --no-feature

脚本:

1
2
3
4
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)

然后,如果要设置其中许多帮助器,则可以使用此帮助器:

1
2
3
4
5
6
7
8
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')


另一种解决方案是使用前面的建议,但使用argparse中的"正确"分析错误:

1
2
3
4
5
6
7
def str2bool(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.')

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

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

允许我使用:

1
2
script --nice
script --nice <bool>

仍然使用默认值(特定于用户设置)。这种方法的一个(间接相关的)缺点是"nargs"可能捕捉到位置参数——请参阅这个相关问题和argparse bug报告。


关于type=booltype='bool'的含义似乎有些困惑。一个(或两个)是指"运行函数EDOCX1"〔5〕还是"返回布尔值"?从目前的情况来看,type='bool'毫无意义。add_argument给出'bool' is not callable错误,与使用type='foobar'type='int'相同。

但是,argparse确实有注册表,允许您这样定义关键字。它主要用于action,例如"action='store'u true"。您可以看到注册关键字:

1
parser._registries

显示字典

1
2
3
4
5
{'action': {None: argparse._StoreAction,
  'append': argparse._AppendAction,
  'append_const': argparse._AppendConstAction,
...
 'type': {None: <function argparse.identity>}}

定义了很多操作,但只有一种类型,默认类型,argparse.identity

此代码定义了一个"bool"关键字:

1
2
3
4
5
6
7
8
9
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): Convert a value to a Boolean, using the standard truth testing procedure.

把这个和

int(x): Convert a number or string x to an integer.


oneliner:

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


这里是另一个没有额外行/秒来设置默认值的变体。bool总是分配一个值,这样就可以在没有预检查的情况下在逻辑语句中使用它。

1
2
3
4
5
6
7
8
9
10
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 == True:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) +" is always a bool")


我也在寻找同样的问题,我想最好的解决办法是:

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

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


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


这适用于我所期望的一切:

1
2
3
4
5
6
7
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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
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')


类似的方法是使用:

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

如果您设置了参数——命令中的特性

1
 command --feature

如果不设置类型,参数将为true——特性参数默认值始终为false!


一个简单的方法是如下使用。

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
2
3
parser.add_argument('--ensure', nargs='*', default=None)

ENSURE = config.ensure is None