Analizando valores booleanos con argparse

Me gustaría usar argparse para analizar argumentos de línea de comandos booleanos escritos como “–foo True” o “–foo False”. Por ejemplo:

my_program --my_boolean_flag False 

Sin embargo, el siguiente código de prueba no hace lo que me gustaría:

 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) 

Lamentablemente, parsed_args.my_bool evalúa como True . Este es el caso incluso cuando cambio cmd_line para que sea ["--my_bool", ""] , lo cual es sorprendente, ya que bool("") evalúa como False .

¿Cómo puedo hacer que argparse para analizar "False" , "F" , y que sus variantes en minúsculas sean False ?

Otra solución más que usa las sugerencias anteriores, pero con el error de análisis “correcto” de argparse :

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

Esto es muy útil para hacer interruptores con valores predeterminados; por ejemplo

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

me permite usar:

 script --nice script --nice  

y sigue utilizando un valor predeterminado (específico para la configuración del usuario). Un inconveniente (relacionado indirectamente) con este enfoque es que los ‘nargs’ pueden captar un argumento posicional: vea esta pregunta relacionada y este informe de errores argparse .

Creo que una forma más canónica de hacerlo es a través de:

 command --feature 

y

 command --no-feature 

argparse soporta esta versión muy bien:

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

Por supuesto, si realmente desea la --arg , podría pasar ast.literal_eval como el “tipo”, o una función definida por el usuario …

 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? 

Recomiendo la respuesta de mgilson pero con un grupo mutuamente exclusivo.
de modo que no puede usar --feature y --no-feature al mismo tiempo.

 command --feature 

y

 command --no-feature 

pero no

 command --feature --no-feature 

Guión:

 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) 

A continuación, puede utilizar este helper si va a configurar muchos de ellos:

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

Parece haber cierta confusión en cuanto a lo que podrían significar type=bool y type='bool' . ¿Debe uno (o ambos) significar ‘ejecutar la función bool() , o’ devolver un booleano ‘? En su forma actual, type='bool' no significa nada. add_argument da un error 'bool' is not callable , igual que si add_argument type='foobar' , o type='int' .

Pero argparse tiene un registro que le permite definir palabras clave como esta. Se utiliza principalmente para la action , por ejemplo, `action = ‘store_true’. Puedes ver las palabras clave registradas con:

 parser._registries 

que muestra un diccionario

 {'action': {None: argparse._StoreAction, 'append': argparse._AppendAction, 'append_const': argparse._AppendConstAction, ... 'type': {None: }} 

Hay muchas acciones definidas, pero solo un tipo, el predeterminado, argparse.identity .

Este código define una palabra clave ‘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() no está documentado, pero tampoco está oculto. En su mayor parte, el progtwigdor no necesita saberlo porque el type y la action toman valores de función y clase. Hay muchos ejemplos de stackoverflow para definir valores personalizados para ambos.


En caso de que no sea obvio en la discusión anterior, bool() no significa “analizar una cadena”. De la documentación de Python:

bool (x): Convierte un valor a Booleano, usando el procedimiento de prueba de verdad estándar.

Contraste esto con

int (x): convierte un número o una cadena x en un entero.

un trazador de líneas:

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

Aquí hay otra variación sin filas / s adicionales para establecer los valores predeterminados. El bool siempre tiene un valor asignado para que pueda usarse en declaraciones lógicas sin comprobaciones previas.

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

Estaba buscando el mismo problema, y ​​ahora la solución bonita es:

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

y usar eso para analizar la cadena a booleano como se sugirió anteriormente.

Además de lo que dijo @mgilson, se debe tener en cuenta que también hay un método ArgumentParser.add_mutually_exclusive_group(required=False) que haría que fuera trivial hacer cumplir ese --flag y --no-flag no se usan al mismo tiempo .

Esto funciona para todo lo que espero:

 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 

El código:

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

Una forma más sencilla sería utilizar como se muestra a continuación.

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

Una forma bastante similar es usar:

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

y si establece el argumento –feature en su comando

  command --feature 

el argumento será verdadero, si no establece el tipo –featura, los argumentos predeterminados siempre son falsos!

 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'--[Az]+', 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) 

Creo que la forma más canónica será:

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