Módulo 2 · Lección 9 de 28
Primeros pasos con argparse
El módulo de la biblioteca estándar que lee la línea de comandos por ti, comprueba lo que falta y escribe la ayuda solo.
La lección anterior terminó con un conversor que funcionaba y con la sensación de que añadirle una sola opción iba a costar más código que el programa entero. Esa sensación es correcta, y es tan vieja como los programas de terminal: leer la línea de comandos parece trivial y no lo es.
Por eso la biblioteca estándar trae argparse. Le describes qué espera tu programa y él se encarga del resto: partir la línea, comprobar que están todos los argumentos obligatorios, quejarse con un mensaje decente cuando no, convertir los valores y escribir la ayuda de -h por su cuenta. No hay que instalar nada.
El esqueleto mínimo#
Son cuatro pasos, siempre los mismos:
"""Saluda a alguien por su nombre."""
import argparse
analizador = argparse.ArgumentParser(description="Saluda a alguien por su nombre.")
analizador.add_argument("nombre", help="a quién hay que saludar")
argumentos = analizador.parse_args()
print(f"Hola, {argumentos.nombre}.")Leídos de arriba abajo:
- Crear el analizador con
argparse.ArgumentParser(). Eldescriptiones la frase que saldrá en la ayuda explicando para qué sirve el programa. - Declarar qué espera, un
add_argumentpor cada cosa. El primer parámetro es el nombre y elhelpes la línea que lo describirá en la ayuda. - Analizar la línea con
parse_args(). Aquí es donde ocurre todo el trabajo. - Usar el resultado, que es un objeto con un atributo por argumento declarado.
Funciona igual que la versión a mano:
python3 saludo.py ErwinHola, Erwin.Lo que devuelve parse_args()#
Un objeto Namespace, que no es más que una cajita con los valores puestos como atributos. Mirémoslo por dentro:
import argparse
analizador = argparse.ArgumentParser(description="Enseña lo que devuelve parse_args.")
analizador.add_argument("origen")
analizador.add_argument("destino")
argumentos = analizador.parse_args()
print(argumentos)
print(type(argumentos))
print(argumentos.origen)
print(vars(argumentos))python3 inspeccion.py informe.md informe.htmlNamespace(origen='informe.md', destino='informe.html')
<class 'argparse.Namespace'>
informe.md
{'origen': 'informe.md', 'destino': 'informe.html'}Es decir: el nombre que le das en add_argument es el nombre del atributo. No hay índices ni posiciones que recordar; el sys.argv[2] de antes se convierte en argumentos.destino, que se lee solo.
La ayuda sale gratis#
Esto es lo que más se agradece. Sin escribir una línea más, tu programa ya entiende -h:
python3 saludo.py -husage: saludo.py [-h] nombre
Saluda a alguien por su nombre.
positional arguments:
nombre a quién hay que saludar
options:
-h, --help show this help message and exitDesmenuzando la salida:
- La línea
usage:la construyeargparsecon lo que has declarado. Elsaludo.pyde delante sale desys.argv[0], así que si renombras el fichero, la ayuda se actualiza sola. Los corchetes de[-h]significan "esto es opcional";nombre, sin corchetes, es obligatorio. - Debajo va el
descriptionque le pasaste al constructor. - Después, un apartado por cada tipo de argumento, con su
helpalineado en columnas. - El
-h, --helpaparece sin que lo declares:argparselo añade a todos los analizadores.
Esa ayuda es la documentación de tu programa y se mantiene sola al día, porque sale de la misma declaración que usa el código. Es imposible que se quede obsoleta.
Los errores también salen gratis#
Lanza el programa sin el argumento obligatorio:
python3 saludo.pyusage: saludo.py [-h] nombre
saludo.py: error: the following arguments are required: nombreCompara eso con el IndexError y su traceback de la lección anterior. Aquí hay un recordatorio de cómo se usa el programa, el nombre del programa y qué falta exactamente. Y si sobran argumentos:
python3 saludo.py Ana Luisusage: saludo.py [-h] nombre
saludo.py: error: unrecognized arguments: LuisEn los dos casos argparse imprime el mensaje y termina el programa por su cuenta: las líneas que van después de parse_args() no llegan a ejecutarse. No tienes que comprobar nada ni salir tú.
Varios argumentos posicionales#
Se declaran uno detrás de otro, y el orden en que los declaras es el orden en que hay que escribirlos:
analizador.add_argument("cantidad", help="el número que hay que convertir")
analizador.add_argument("origen", help="unidad de partida: m, km, milla, pie o pulgada")
analizador.add_argument("destino", help="unidad de llegada")argparse los reparte por posición, igual que harías tú con sys.argv[1], sys.argv[2] y sys.argv[3], pero contando él y avisando cuando la cuenta no sale.
El conversor, reescrito#
Este es el mismo programa de la lección anterior, con argparse en lugar de la comprobación a mano:
"""Convierte una cantidad entre unidades de longitud."""
import argparse
import sys
METROS_POR_UNIDAD = {
"m": 1.0,
"km": 1000.0,
"milla": 1609.344,
"pie": 0.3048,
"pulgada": 0.0254,
}
def convertir(cantidad: float, origen: str, destino: str) -> float:
"""Pasa la cantidad de la unidad de origen a la de destino."""
metros = cantidad * METROS_POR_UNIDAD[origen]
return metros / METROS_POR_UNIDAD[destino]
def main() -> None:
analizador = argparse.ArgumentParser(
description="Convierte una cantidad entre unidades de longitud.",
)
analizador.add_argument("cantidad", help="el número que hay que convertir")
analizador.add_argument("origen", help="unidad de partida: m, km, milla, pie o pulgada")
analizador.add_argument("destino", help="unidad de llegada")
argumentos = analizador.parse_args()
try:
cantidad = float(argumentos.cantidad)
except ValueError:
print(f"'{argumentos.cantidad}' no es un número.")
sys.exit(1)
if argumentos.origen not in METROS_POR_UNIDAD or argumentos.destino not in METROS_POR_UNIDAD:
print(f"Unidad desconocida. Usa una de: {', '.join(METROS_POR_UNIDAD)}")
sys.exit(1)
resultado = convertir(cantidad, argumentos.origen, argumentos.destino)
print(f"{cantidad:g} {argumentos.origen} = {resultado:.4g} {argumentos.destino}")
if __name__ == "__main__":
main()Hace exactamente lo mismo que antes:
python3 conversor.py 42 km milla42 km = 26.1 millaPero ahora tiene ayuda, que antes no tenía:
python3 conversor.py -husage: conversor.py [-h] cantidad origen destino
Convierte una cantidad entre unidades de longitud.
positional arguments:
cantidad el número que hay que convertir
origen unidad de partida: m, km, milla, pie o pulgada
destino unidad de llegada
options:
-h, --help show this help message and exitY el control de la cantidad de argumentos es mucho más fino. Antes, cualquier número distinto de tres daba el mismo mensaje de uso; ahora argparse dice exactamente cuáles faltan:
python3 conversor.py 42usage: conversor.py [-h] cantidad origen destino
conversor.py: error: the following arguments are required: origen, destinopython3 conversor.py 42 km milla extrausage: conversor.py [-h] cantidad origen destino
conversor.py: error: unrecognized arguments: extraQué ha cambiado y qué no#
| Antes, a mano | Ahora, con argparse |
|---|---|
if len(sys.argv) != 4 y un mensaje de uso escrito a mano | Nada: lo comprueba parse_args() |
sys.argv[2], hay que recordar qué había en cada posición | argumentos.origen |
| El texto de uso se escribe y se mantiene aparte | La ayuda sale de los add_argument |
No hay -h | -h funciona desde el primer día |
| Sale con código 1 pase lo que pase | Sale con código 2 al invocarlo mal |
Lo que no ha cambiado es el bloque del float() y el de las unidades. Esas dos comprobaciones siguen hechas a mano, y no hace falta: argparse sabe convertir a número y sabe limitar los valores admitidos. Lo verás en la lección de tipos y validación, y las dos desaparecerán del código.
El mapa de lo que viene#
Todo lo que se puede configurar de un argumento va en add_argument. De momento has visto uno, help, y este es el panorama completo:
| Parámetro | Para qué | Dónde se ve |
|---|---|---|
help | La línea que describe el argumento en la ayuda | Esta lección |
default | Valor si no lo escriben | Opciones y banderas |
action | Qué hacer con el argumento: guardarlo, contarlo, acumularlo | Opciones y banderas |
type | A qué convertirlo antes de guardarlo | Tipos y validación |
choices | Los únicos valores admitidos | Tipos y validación |
nargs | Cuántos valores consume | Tipos y validación |
metavar | Con qué nombre aparece en la ayuda | Ayuda y documentación |