【问题标题】:Parsing boolean values with argparse使用 argparse 解析布尔值
【发布时间】:2013-02-07 03:51:14
【问题描述】:

我想使用 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

【问题讨论】:

  • 这里是@mgilson's answerparser.add_argument('--feature', dest='feature', default=False, action='store_true')的单行解释。此解决方案将确保您始终获得具有值 TrueFalsebool 类型。 (此解决方案有一个约束:您的选项必须具有默认值。)
  • 这里是对@Maxim's answerparser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x)))的单行解释。使用该选项时,此解决方案将确保 bool 类型的值为 TrueFalse。如果不使用该选项,您将获得None。 (distutils.util.strtobool(x) is from another stackoverflow question)
  • parser.add_argument('--my_bool', action='store_true', default=False)这样的东西怎么样
  • 对于@TrevorBoydSmith 的回答,请尝试使用import distutils.util 而不是import disutils 导入。见this answer
  • 刚刚遇到了同样的问题。令人震惊的是 argparse 模块有多么不必要的大和杂草丛生,但它仍然没有做它应该开箱即用的简单事情。更糟糕的是,它做错了。

标签: python boolean argparse command-line-arguments


【解决方案1】:

这实际上已经过时了。对于 Python 3.7+,Argparse now supports boolean args(搜索 BooleanOptionalAction)。

实现如下所示:

import argparse

ap = argparse.ArgumentParser()

# List of args
ap.add_argument('--foo', default=True, type=bool, help='Some helpful text that is not bar. Default = True')

# Importable object
args = ap.parse_args()

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

【讨论】:

  • 你提到这个工作在 3.7+ 和 3.9+ 中。是哪一个?
  • 3.7+。在编辑中澄清
  • python 文档中提到了New in version 3.9,我无法在 3.7 中从 argparse 导入 BooleanOptionalAction...
【解决方案2】:

使用先前建议的另一个解决方案,但来自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”可能会捕捉到位置参数——参见this related questionthis argparse bug report

【讨论】:

  • nargs='?'表示零个或一个参数。 docs.python.org/3/library/argparse.html#nargs
  • 我喜欢这个,但我相当于 default=NICE 给了我一个错误,所以我必须做点别的。
  • @MarcelloRomani str2bool 不是 Python 意义上的类型,它是上面定义的函数,你需要在某个地方包含它。
  • str2bool(v) 的代码可以替换为bool(distutils.util.strtobool(v))。来源:stackoverflow.com/a/18472142/2436175
  • 也许值得一提的是,通过这种方式,您无法检查参数是否设置为if args.nice:,因为如果参数设置为 False,它将永远不会通过条件。如果这是正确的,那么最好从str2bool 函数返回列表并将列表设置为const 参数,例如[True][False]。如果我错了,请纠正我
【解决方案3】:

在之前关注@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 不会返回除01 之外的任何其他值,python 会将它们无缝且一致地转换为bool 值。

>>> 0 == False
True
>>> 0 == True
False
>>> 1 == False
False
>>> 1 == True
True

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

python yours.py --feature wrong_value

lambda 相比,带有strtobool 的argparse.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'

【讨论】:

    【解决方案4】:

    转换值:

    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 True
            elif arg in str_false_values:
                return False
    
        if isinstance(arg, int):
            if arg == 1:
                return True
            elif 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 error
        return False
    
    [...]
    
    
    args = ap.parse_args()
    my_arg = options.my_arg
    my_arg = __arg_to_bool__(my_arg)
    
    

    【讨论】:

      【解决方案5】:

      最简单最正确的方法是:

      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。

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

        • 这个答案被低估了,但它的简单却很棒。不要尝试设置 required=True 否则你总是会得到一个 True arg。
        • 切勿对 bool 或 nonetype 之类的东西使用相等运算符。你应该使用 IS 代替
        • 这是一个比接受的更好的答案,因为它只是检查标志的存在以设置布尔值,而不是需要冗余的布尔字符串。 (哟 dawg,我听说你喜欢布尔值......所以我给你一个布尔值和你的布尔值来设置你的布尔值!)
        • 嗯...问题,如前所述,似乎想在命令行本身上使用“True”/“False”;然而,在这个例子中,python3 test.py --do-something Falseerror: unrecognized arguments: False 失败,所以它并没有真正回答问题。
        • 一个小提示:默认的 None 通常在这里也可以正常工作。
        【解决方案7】:

        如果你想同时允许--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')
        

        【讨论】:

        • @CharlieParker add_argumentdest='feature' 调用。 set_defaultsfeature=True 调用。明白了吗?
        • 这个或 mgilson 的答案应该是公认的答案 - 即使 OP 想要 --flag False,部分 SO 答案应该是关于他们想要解决的问题,而不仅仅是关于如何。绝对没有理由这样做 --flag False--other-flag True 然后使用一些自定义解析器将字符串转换为布尔值。action='store_true'action='store_false' 是使用布尔标志的最佳方法
        • @cowlinator 为什么最终要回答“所陈述的问题”?根据its own guidelines,anwer ... can be “don’t do that”, but it should also include “try this instead”(至少对我而言)暗示答案应该在适当的时候更深入。有时我们中的一些人发布问题可以从更好/最佳实践等方面的指导中受益。回答“如前所述”通常不会做到这一点。话虽如此,您对经常假设太多(或错误)的答案感到沮丧是完全正确的。
        • 如果想要在用户没有明确指定特征的情况下获得第三个值,他需要将最后一行替换为parser.set_defaults(feature=None)
        • 如果我们想为这个参数添加一个help= 条目,它应该放在哪里?在add_mutually_exclusive_group() 通话中?在一个或两个add_argument() 电话中?其他地方?
        【解决方案8】:

        只需执行以下操作,您就可以使用--test = True

        python 文件名 --test

        parser.add_argument("--test" , default=False ,help="test ?", dest='test', action='store_true')
        

        【讨论】:

        【解决方案9】:

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

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

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

        【讨论】:

        • 这看起来确实很方便。我注意到你有 eval 作为类型。我对此有一个疑问:应该如何定义 eval,或者是否需要导入才能使用它?
        • eval 是一个内置函数。 docs.python.org/3/library/functions.html#eval 这可以是其他更灵活的方法利用的任何一元函数。
        • 这很可爱,但是如果不知道eval being evil 的用户只会将其复制粘贴到他们的脚本中,那么将其放到野外是很冒险的。
        • @Arne,说得好。不过,好心的用户似乎很难不小心做出有害的事情。
        • 不要使用。不仅不安全,最重要的答案也更加惯用。如果你还想走这条路,已经提到了一个流行的答案:ast.literal_eval 更安全。
        【解决方案10】:

        作为对@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
        

        【讨论】:

          【解决方案11】:

          与@Akash 类似,但这是我使用的另一种方法。它使用str 而不是lambda 因为python 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")
          

          【讨论】:

            【解决方案12】:

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

            【讨论】:

            • 这是最好的方法,0 和 1 很容易解释为 False 和 True。但是,您应该更正您的第一条语句,说 0 将返回 false,而 任何其他值 将返回 True。如果您确实想限制为 0,1,请添加如下选项:parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)), choices=['0','1'])
            【解决方案13】:

            最简单的方法是使用选择

            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。如果您始终希望用户明确指定选择,则可以添加 required=True 选项。

            【讨论】:

              【解决方案14】:

              单线:

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

              【讨论】:

              • 很适合oneliner的粉丝,也可以改进一下:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
              • 另一种选择是使用标准的distutils.utils.strtobool,例如type=lambda x: bool(strtobool(str(x)))。真值为 y、yes、t、true、on 和 1; false 值为 n、no、f、false、off 和 0。
              【解决方案15】:

              我认为最规范的方式是:

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

              【讨论】:

                【解决方案16】:

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

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

                【讨论】:

                  【解决方案17】:

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

                  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 &lt;True|False&gt; 版本,您可以将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?
                  

                  【讨论】:

                  • 我仍然认为type=bool 应该开箱即用(考虑位置参数!)。即使您另外指定choices=[False,True],您最终也会将“False”和“True”都视为True(由于从字符串转换为布尔值?)。 Maybe related issue
                  • 对,我只是认为没有理由不按预期工作。这是极具误导性的,因为没有安全检查或错误消息。
                  • @mgilson -- 我发现误导的是你可以设置 type=bool,你没有收到错误消息,但是,对于“False”和“True”字符串参数,你在你所谓的布尔变量中得到 True (由于类型转换在 python 中的工作方式)。因此,要么 type=bool 显然不被支持(发出一些警告、错误等),要么它应该以一种有用且直观预期的方式工作。
                  • @dolphin -- 我不同意。我认为这种行为正是它应有的方式,并且符合 python 的禅宗“特殊情况不足以打破规则”。但是,如果您对此有强烈的感觉,为什么不在各种 python mailing lists 之一上提出它呢?在那里,您可能有机会说服有权对这个问题某事的人。即使你能说服我,你也只是成功地说服了我,行为仍然不会改变,因为我不是开发人员:)
                  • 我们是否在争论 Python bool() 函数应该做什么,或者 argparse 应该在 type=fn 中接受什么?所有argparse 检查是fn 是可调用的。它期望fn 接受一个字符串参数,并返回一个值。 fn 的行为是程序员的责任,不是argparse's
                  【解决方案18】:

                  这适用于我期望的一切:

                  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'}
                  【解决方案19】:

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

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

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

                   command --feature
                  

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

                  【讨论】:

                  • 这种方法有其他答案克服的缺点吗?这似乎是迄今为止最简单、最简洁的解决方案,可以满足 OP(在这种情况下是我)想要的。我喜欢它。
                  • 虽然简单,但并不能回答问题。 OP想要一个参数,您可以在其中指定--feature False
                  【解决方案20】:
                  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)
                  

                  【讨论】:

                    【解决方案21】:

                    对于type=booltype='bool' 的含义似乎有些混淆。一个(或两者)应该意味着'运行函数bool(),还是'返回一个布尔值'?就目前而言,type='bool' 没有任何意义。 add_argument 给出 'bool' is not callable 错误,就像您使用 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() 没有记录,但也没有隐藏。在大多数情况下,程序员不需要知道它,因为typeaction 采用函数和类值。有很多为两者定义自定义值的 stackoverflow 示例。


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

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

                    对比一下

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

                    【讨论】:

                    • 或者使用:parser.register('type', 'bool', (lambda x: x.lower() in ("yes", "true", "t", "1") ))
                    【解决方案22】:

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

                    【讨论】:

                      【解决方案23】:

                      我正在寻找同样的问题,恕我直言,漂亮的解决方案是:

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

                      并按照上面的建议使用它来将字符串解析为布尔值。

                      【讨论】:

                      • 如果你要走这条路,我建议distutils.util.strtobool(v)
                      • distutils.util.strtobool 返回 1 或 0,而不是实际的布尔值。
                      猜你喜欢
                      • 2020-07-14
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-05-13
                      • 2014-11-30
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多