Tenga cuidado al tratar con valores booleanos en el argparse de Python

Negocio

Para manejar los argumentos de la línea de comandos en Python, utilice los módulos argv o argparse del módulo sys.

El módulo argparse permite un manejo flexible de los argumentos de la línea de comandos, pero hay que tener cuidado cuando se trata de valores booleanos (true, false).

La siguiente información se proporciona aquí.

  • argparse para facilitar la definición de los argumentos
  • Especifica el tipo de argumento (type) con argparse
  • No especificar «bool» como tipo de argumento de add_argument()
  • Juicio por bool()
  • Utilice la acción del argumento en lugar del tipo de argumento.
  • Uso de la función strtobool()

argparse para facilitar la definición de los argumentos

El módulo argparse facilita la definición de argumentos en la línea de comandos.

El módulo argparse facilita la creación de interfaces de línea de comandos fáciles de usar. El módulo argparse genera automáticamente mensajes de ayuda y de uso, y genera un error si el usuario especifica argumentos no válidos para el programa.
argparse — Parser for command-line options, arguments and sub-commands — Python 3.10.0 Documentation

Especifica el tipo de argumento (type) con argparse

Una característica útil de argparse es especificar el tipo (type).

Por ejemplo, si especifica un tipo entero (int), convertirá automáticamente el argumento a int y también emitirá un error para los argumentos que no sean int.

El tipo está especificado por el tipo de argumento de add_argument().

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('arg_int', type=int)

args = parser.parse_args()
print(args.arg_int)
print(type(args.arg_int))

Ejecute este archivo desde la línea de comandos.

$ python argparse_type_int.py 100
100
<type 'int'>

El argumento 100 se lee como int.

Si se utiliza un valor no-int como argumento, se producirá un error.

$ python argparse_type_int.py foo
usage: argparse_type_int.py [-h] arg_int
argparse_type_int.py: error: argument arg_int: invalid int value: 'foo'

$ python argparse_type_int.py 1.23
usage: argparse_type_int.py [-h] arg_int
argparse_type_int.py: error: argument arg_int: invalid int value: '1.23'

Muy útil para reproducir argumentos inesperados.

No especificar «bool» como tipo de argumento de add_argument()

Es importante tener en cuenta que bool, al igual que int y float, no funcionará como se espera si se especifica bool como tipo de argumento de add_argument().

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('arg_bool', type=bool)

args = parser.parse_args()
print(args.arg_bool)
print(type(args.arg_bool))

Ejecute este archivo desde la línea de comandos.

$ python argparse_type_bool.py True
True
<type 'bool'>

Si se utiliza true como argumento, se leerá como un bool de tipo true. Este es el comportamiento esperado, pero el problema es el siguiente caso.

$ python argparse_type_bool.py False
True
<type 'bool'>

$ python argparse_type_bool.py bar
True
<type 'bool'>

Si se utiliza false o cualquier otra cadena como argumento, se leerá como true.

La razón por la que esto ocurre es que cuando se especifica type=xxx en add_argument(), el argumento se pasa a xxx().

Por ejemplo, si type=int, el argumento se pasará a int(); si type=float, entonces float().

Lo mismo ocurre con type=bool, que significa que el argumento se pasará a bool().

Juicio por bool()

Este bool() es complicado.

Los siguientes valores se consideran falsos:

  • None
  • false
  • Cero en los tipos numéricos. Por ejemplo, los siguientes valores
    • 0
    • 0.0
    • 0j
  • Una secuencia vacía. Por ejemplo
    • ''
    • ()
    • []
  • Mapeo vacío. Por ejemplo
    • {}

Todos los demás valores se asumen como verdaderos, por lo que los objetos de muchos tipos son siempre verdaderos. Las operaciones y funciones incorporadas que devuelven resultados booleanos siempre devuelven 0 o False como valor falso y 1 o True como valor verdadero, a menos que se indique lo contrario.

Por lo tanto, todas las cadenas no vacías pasadas a bool(), ya sean 'true' o 'false', devolverán true. Sólo las cadenas vacías serán falsas.

print(bool('True'))
print(bool('False'))
print(bool('abc'))
# True
# True
# True

print(bool(''))
# False

Cuando se establece type=bool en add_argument(), el argumento se pasa a bool(). Por lo tanto, como se muestra en el ejemplo anterior, si se utiliza false como argumento, será convertido por bool() como la cadena 'False' y leído como true.

Utilice la acción del argumento en lugar del tipo de argumento.

Si desea utilizar valores booleanos en argparse, especifique 'store_true' o 'store_false' para la acción del argumento.

  • 'store_true'
  • 'store_false'

Serán versiones especiales de 'store_const' que almacenarán True y False respectivamente. Además, establecerán los valores por defecto en False y True respectivamente, en ese orden.
argparse — Parser for command-line options, arguments and sub-commands — Python 3.10.0 Documentation

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--en', action='store_true')

args = parser.parse_args()
print(args.en)
print(type(args.en))

En este ejemplo, se dan las siguientes opciones.
--enPor lo tanto, si en no se establece como verdadero, se cargará como falso, que es el valor por defecto de en.

$ python argparse_option_bool.py --en
True
<type 'bool'>

$ python argparse_option_bool.py
False
<type 'bool'>

Si quieres establecer el valor por defecto en true, y false cuando se añada la opción, sólo tienes que hacer lo siguiente.
action='store_false'

Uso de la función strtobool()

Si desea utilizar argumentos posicionales en lugar de opciones, también puede utilizar la función strtobool().

strtobool() es una función que convierte una cadena en verdadera (1) o falsa (0).

Convierte una cadena booleana en verdadera (1) o falsa (0).
Los valores reales son los siguientes

  • y
  • yes
  • true
  • on
  • 1

Los valores falsos son los siguientes.

  • n
  • no
  • f
  • false
  • off
  • 0

Si val no es ninguno de los anteriores, se produce un ValueError.

9. API Reference – strtobool() — Python 3.10.0 Documentation

No distingue entre mayúsculas y minúsculas, así que, por ejemplo, puede utilizar lo siguiente; cualquier otra cadena dará lugar a un error.

  • 'TRUE'
  • 'True'
  • 'YES'
from distutils.util import strtobool

print(strtobool('true'))
print(strtobool('True'))
print(strtobool('TRUE'))
# 1
# 1
# 1

print(strtobool('t'))
print(strtobool('yes'))
print(strtobool('y'))
print(strtobool('on'))
print(strtobool('1'))
# 1
# 1
# 1
# 1
# 1

print(strtobool('false'))
print(strtobool('False'))
print(strtobool('FALSE'))
# 0
# 0
# 0

print(strtobool('f'))
print(strtobool('no'))
print(strtobool('n'))
print(strtobool('off'))
print(strtobool('0'))
# 0
# 0
# 0
# 0
# 0

# print(strtobool('abc'))
# ValueError: invalid truth value 'abc'

El nombre es strtobool(), pero el valor de retorno no es bool, sino int (1 o 0).

print(type(strtobool('true')))
# <class 'int'>

Como se ha escrito anteriormente, cuando se especifica type=xxx en add_argument() de argparse, el argumento se pasará a xxx(). Por lo tanto, podemos hacer lo siguiente.
type=strtobool

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument('arg_bool', type=strtobool)

args = parser.parse_args()
print(args.arg_bool)
print(type(args.arg_bool))

El valor de retorno no es de tipo bool, sino de tipo int 1 o 0, pero puede leer valores verdaderos o falsos con true o false como argumentos.

$ python argparse_type_strtobool.py true
1
<type 'int'>

$ python argparse_type_strtobool.py false
0
<type 'int'>

Además, si el argumento no es el esperado, se generará un error correctamente.

$ python argparse_type_strtobool.py bar
usage: argparse_type_strtobool.py [-h] arg_bool
argparse_type_strtobool.py: error: argument arg_bool: invalid strtobool value: 'bar'