Saltar al contenido
🐍 Curso de Python
/

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.

⏱ 15 min de lectura

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:

saludo.py
"""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:

  1. Crear el analizador con argparse.ArgumentParser(). El description es la frase que saldrá en la ayuda explicando para qué sirve el programa.
  2. Declarar qué espera, un add_argument por cada cosa. El primer parámetro es el nombre y el help es la línea que lo describirá en la ayuda.
  3. Analizar la línea con parse_args(). Aquí es donde ocurre todo el trabajo.
  4. Usar el resultado, que es un objeto con un atributo por argumento declarado.

Funciona igual que la versión a mano:

bash
python3 saludo.py Erwin
Salida en pantalla
Hola, 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:

inspeccion.py
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))
bash
python3 inspeccion.py informe.md informe.html
Salida en pantalla
Namespace(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:

bash
python3 saludo.py -h
Salida en pantalla
usage: 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 exit

Desmenuzando la salida:

  • La línea usage: la construye argparse con lo que has declarado. El saludo.py de delante sale de sys.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 description que le pasaste al constructor.
  • Después, un apartado por cada tipo de argumento, con su help alineado en columnas.
  • El -h, --help aparece sin que lo declares: argparse lo 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:

bash
python3 saludo.py
Salida en pantalla
usage: saludo.py [-h] nombre
saludo.py: error: the following arguments are required: nombre

Compara 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:

bash
python3 saludo.py Ana Luis
Salida en pantalla
usage: saludo.py [-h] nombre
saludo.py: error: unrecognized arguments: Luis

En 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:

python
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:

conversor.py
"""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:

bash
python3 conversor.py 42 km milla
Salida en pantalla
42 km = 26.1 milla

Pero ahora tiene ayuda, que antes no tenía:

bash
python3 conversor.py -h
Salida en pantalla
usage: 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 exit

Y 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:

bash
python3 conversor.py 42
Salida en pantalla
usage: conversor.py [-h] cantidad origen destino
conversor.py: error: the following arguments are required: origen, destino
bash
python3 conversor.py 42 km milla extra
Salida en pantalla
usage: conversor.py [-h] cantidad origen destino
conversor.py: error: unrecognized arguments: extra

Qué ha cambiado y qué no#

Antes, a manoAhora, con argparse
if len(sys.argv) != 4 y un mensaje de uso escrito a manoNada: lo comprueba parse_args()
sys.argv[2], hay que recordar qué había en cada posiciónargumentos.origen
El texto de uso se escribe y se mantiene aparteLa ayuda sale de los add_argument
No hay -h-h funciona desde el primer día
Sale con código 1 pase lo que paseSale 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ámetroPara quéDónde se ve
helpLa línea que describe el argumento en la ayudaEsta lección
defaultValor si no lo escribenOpciones y banderas
actionQué hacer con el argumento: guardarlo, contarlo, acumularloOpciones y banderas
typeA qué convertirlo antes de guardarloTipos y validación
choicesLos únicos valores admitidosTipos y validación
nargsCuántos valores consumeTipos y validación
metavarCon qué nombre aparece en la ayudaAyuda y documentación