mutually - metavar python
Analizando valores booleanos con argparse (13)
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
?
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 .
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")
Creo que la forma más canónica será:
parser.add_argument(''--ensure'', nargs=''*'', default=None)
ENSURE = config.ensure is None
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 <True|False>
, 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?
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.
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'')
Otra solución más que utiliza 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 <bool>
y seguir utilizando un valor predeterminado (específico para la configuración del usuario). Un inconveniente (relacionado indirectamente) con ese enfoque es que los ''nargs'' pueden captar un argumento posicional: vea esta pregunta relacionada y este informe de error argparse .
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: <function argparse.identity>}}
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 programador no necesita saberlo porque el type
y la action
toman valores de función y clase. Hay muchos ejemplos de 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.
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)
Luego puede usar este asistente 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'')
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 Falso!
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''])
un trazador de líneas:
parser.add_argument(''--is_debug'', default=False, type=lambda x: (str(x).lower() == ''true''))
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)