Análisis de valores booleanos con argparse


616

Me gustaría utilizar 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)

Tristemente, parsed_args.my_boolevalúa a True. Este es el caso incluso cuando cambio cmd_linea ser ["--my_bool", ""], lo cual es sorprendente, ya que bool("")evalúa False.

¿Cómo puedo obtener argparse a analizar "False", "F"y su minúscula variantes que se vayan False?


40
Aquí hay una interpretación de la respuesta de @mgilson parser.add_argument('--feature', dest='feature', default=False, action='store_true') . Esta solución le garantizará que siempre obtenga un booltipo con valor Trueo False. (Esta solución tiene una restricción: su opción debe tener un valor predeterminado).
Trevor Boyd Smith

77
Aquí hay una interpretación de la respuesta de @ Maximparser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Cuando se utiliza la opción, esta solución garantizará un booltipo con valor de Trueo False. Cuando no se utiliza la opción, obtendrá None. ( distutils.util.strtobool(x)es de otra pregunta de stackoverflow )
Trevor Boyd Smith

8
¿ parser.add_argument('--my_bool', action='store_true', default=False)
Qué tal

Respuestas:


276

Otra solución más usando las sugerencias anteriores, pero con el error de análisis "correcto" de 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.')

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

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

me permite usar:

script --nice
script --nice <bool>

y aún usa un valor predeterminado (específico para la configuración del usuario). Una desventaja (indirectamente relacionada) con ese enfoque es que los 'nargs' pueden atrapar un argumento posicional: vea esta pregunta relacionada y este informe de error de argparse .


44
nargs = '?' significa cero o un argumento. docs.python.org/3/library/argparse.html#nargs
Maxim

1
Me encanta esto, pero mi equivalente de default = NICE me está dando un error, por lo que debo hacer otra cosa.
Michael Mathews

2
@MarcelloRomani str2bool no es un tipo en el sentido de Python, es la función definida anteriormente, debe incluirla en alguna parte.
Maxim

44
el código de str2bool(v)podría ser reemplazado por bool(distutils.util.strtobool(v)). Fuente: stackoverflow.com/a/18472142/2436175
Antonio

44
Quizás valga la pena mencionar que de esta manera no se puede verificar si el argumento está configurado if args.nice:porque si el argumento está configurado como False, nunca pasará la condición. Si esto es correcto, entonces quizás es mejor a la lista de volver de str2boolla función y la lista de canciones como constparámetro, como este [True], [False].
Corrígeme

889

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

command --feature

y

command --no-feature

argparse admite 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 quieres la --arg <True|False>versión, puedes pasarla ast.literal_evalcomo "tipo" o como 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?

96
Todavía creo que type=booldebería funcionar fuera de la caja (¡considere los argumentos posicionales!) Incluso cuando especificas adicionalmente choices=[False,True], terminas con "Falso" y "Verdadero" considerado Verdadero (¿debido a un lanzamiento de cadena a bool?). Tal vez un problema relacionado
delfín

41
Bien, creo que no hay justificación para que esto no funcione como se esperaba. Y esto es extremadamente engañoso, ya que no hay controles de seguridad ni mensajes de error.
Delfín

69
@mgilson: lo que encuentro engañoso es que puede establecer type = bool, no recibe ningún mensaje de error y, sin embargo, para los argumentos de cadena "False" y "True", obtiene True en su variable supuestamente booleana (debido a cómo tipo de fundición funciona en python). Por lo tanto, type = bool debería ser claramente incompatible (emitir alguna advertencia, error, etc.), o debería funcionar de una manera que sea útil e intuitivamente esperada.
Delfín

14
@dolphin - respectivamente, no estoy de acuerdo. Creo que el comportamiento es exactamente como debería ser y es consistente con el zen de Python "Los casos especiales no son lo suficientemente especiales como para romper las reglas". Sin embargo, si siente esto fuertemente al respecto, ¿por qué no aparece en una de las diversas listas de correo de Python ? Allí, puede tener la oportunidad de convencer a alguien que tiene el poder de hacer algo al respecto. Incluso si pudieras convencerme, solo habrás logrado convencerme y el comportamiento aún no cambiará ya que no soy un desarrollador :)
mgilson

15
¿Estamos discutiendo sobre lo que bool()debería hacer la función Python , o qué argumento debería aceptar type=fn? Todos los argparsecheques fnson invocables. Espera fntomar un argumento de cadena y devolver un valor. El comportamiento de fnes responsabilidad del programador, no argparse's.
hpaulj

235

Recomiendo la respuesta de mgilson pero con un grupo mutuamente excluyente
para que no pueda usar --featurey --no-featureal 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')

55
@CharlieParker add_argumentse llama con dest='feature'. set_defaultsse llama con feature=True. ¿Entender?
fnkr

44
Esta o la respuesta de mgilson debería haber sido la respuesta aceptada, a pesar de que el OP quería --flag False, parte de las respuestas de SO deberían ser sobre QUÉ están tratando de resolver, no solo sobre CÓMO. No debería haber absolutamente ninguna razón para hacer --flag Falseo --other-flag Trueusar un analizador personalizado para convertir la cadena a booleana ... action='store_true'y action='store_false'son las mejores formas de usar banderas booleanas
kevlarr

66
@cowlinator ¿Por qué SO se trata en última instancia de responder "preguntas como se indica"? De acuerdo con sus propias pautas , una respuesta ... can be “don’t do that”, but it should also include “try this instead”que (al menos para mí) implica respuestas debe profundizar cuando sea apropiado. Definitivamente, hay momentos en que algunos de nosotros que publicamos preguntas pueden beneficiarnos de la orientación sobre mejores / mejores prácticas, etc. Responder "como se dice" a menudo no hace eso. Dicho esto, su frustración con las respuestas a menudo suponiendo demasiado (o incorrectamente) es completamente válida.
kevlarr

2
Si uno quiere tener un tercer valor para cuando el usuario no ha especificado la función explícitamente, debe reemplazar la última línea con elparser.set_defaults(feature=None)
Alex Che

2
Si queremos agregar una help=entrada para este argumento, ¿a dónde debería ir? En la add_mutually_exclusive_group()llamada? ¿En una o ambas add_argument()llamadas? ¿En algún otro lugar?
Ken Williams

57

Aquí hay otra variación sin fila / s extra para establecer 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:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) + " is always a bool")

66
Esta respuesta es subestimada, pero maravillosa en su simplicidad. No intentes configurarlo required=Trueo de lo contrario siempre obtendrás un argumento verdadero.
Garren S

1
Por favor, NUNCA utilice operador de igualdad en cosas como bool o NoneType. Deberías usar IS en su lugar
webKnjaZ

2
Esta es una mejor respuesta que la aceptada porque simplemente verifica la presencia de la bandera para establecer el valor booleano, en lugar de requerir una cadena booleana redundante. (Hola, te escuché como booleanos ... ¡así que te di un booleano con tu booleano para configurar tu booleano!)
Siphon

44
Hmm ... la pregunta, como se dijo, parece querer usar "Verdadero" / "Falso" en la línea de comando; sin embargo, con este ejemplo, python3 test.py --do-something Falsefalla error: unrecognized arguments: False, por lo que realmente no responde la pregunta.
sdbbs

38

un trazador de líneas:

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

44
bueno para fan de oneliner, también podría mejorarse un poco:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui el

35

Parece haber cierta confusión en cuanto a qué type=booly type='bool'podría significar. ¿Debería uno (o ambos) significar 'ejecutar la función bool()o' devolver un booleano '? Tal como está type='bool', no significa nada. add_argumentda un 'bool' is not callableerror, igual que si usara type='foobar', otype='int' .

Pero argparsetiene un registro que le permite definir palabras clave como esta. Se utiliza principalmente para action, por ejemplo, `action = 'store_true'. Puede 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 saber al respecto porque typey actionvalores de la función toma y de clase. Hay muchos ejemplos de stackoverflow para definir valores personalizados para ambos.


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

bool (x): convierte un valor a un booleano, utilizando el procedimiento estándar de prueba de verdad.

Contrasta esto con

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


3
O use: parser.register ('type', 'bool', (lambda x: x.lower () en ("yes", "true", "t", "1")))
Matyas

17

Estaba buscando el mismo problema, y ​​en mi humilde opinión, la bonita solución 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.


55
Si vas a ir por esta ruta, te sugiero distutils.util.strtobool(v).
CivFan

1
Los distutils.util.strtoboolrendimientos de 1 o 0, no un booleano real.
CMCDragonkai

14

Una forma bastante similar es usar:

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

y si establece el argumento --característica en su comando

 command --feature

el argumento será Verdadero, si no establece el tipo --característica, el argumento predeterminado siempre es Falso!


1
¿Hay algún inconveniente en este método que las otras respuestas superen? Esta parece ser, con mucho, la solución más fácil y sucinta que llega a lo que quería el OP (y en este caso yo). Me encanta.
Simon O'Hanlon

2
Si bien es simple, no responde la pregunta. OP quiere un argumento donde puede especificar--feature False
Astariul


12

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

¡Excelente! Me voy con esta respuesta. Ajusté mi _str_to_bool(s)para convertir s = s.lower()una vez, luego probar if s not in {'true', 'false', '1', '0'}y finalmente return s in {'true', '1'}.
Jerry101

6

Una forma más sencilla sería usar la siguiente.

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

5

Más simple No es flexible, pero prefiero la simplicidad.

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

EDITAR: si no confía en la entrada, no la use eval.


Esto parece bastante conveniente. Noté que tienes eval como el tipo. Tenía una pregunta sobre esto: ¿cómo debería definirse eval, o hay una importación requerida para poder usarla?
edesz

1
evalEs una función incorporada. docs.python.org/3/library/functions.html#eval Esta puede ser cualquier función unaria que aprovechan otros enfoques más flexibles.
Russell

Oye, eso es genial. ¡Gracias!
edesz

2
eso es lindo, pero bastante arriesgado simplemente ponerlo en la naturaleza donde los usuarios que no son conscientes de que Eva es malvado lo copiarán y pegarán en sus scripts.
Arne

@Arne, buen punto. Sin embargo, parece que sería bastante difícil para un usuario bien intencionado hacer accidentalmente algo pernicioso.
Russell

3

La forma más sencilla sería usar opciones :

parser = argparse.ArgumentParser()
parser.add_argument('--my-flag',choices=('True','False'))

args = parser.parse_args()
flag = args.my_flag == 'True'
print(flag)

No pasa --mi-flag se evalúa como False. La opción required = True podría agregarse si siempre desea que el usuario especifique explícitamente una opción.


2

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

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

ENSURE = config.ensure is None

1
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

La forma más simple y correcta es

from distutils import util
arser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x)))

Tenga en cuenta que los valores verdaderos son y, yes, t, true, on y 1; los valores falsos son n, no, f, falso, apagado y 0. Aumenta ValueError si val es otra cosa.


0

Rápido y fácil, pero solo para los argumentos 0 o 1:

parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)))
myargs=parser.parse_args()
print(myargs.mybool)

El resultado será "Falso" después de llamar desde la terminal:

python myscript.py 0

-1

Similar a @Akash pero aquí hay otro enfoque que he usado. Utiliza strque lambdadebido pitón lambdasiempre me da un extranjero sentimientos.

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

-1

Como una mejora a la respuesta de @Akash Desarda, podrías hacer

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)

Y es compatible 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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.