Cómo crear una CLI en Python con argparse

Actualizado el: 11/07/2026
Tempo de leitura: 3 minutos
Logo do Python com o texto 'Python argparse CLI' abaixo

Una interfaz de línea de comandos convierte un script en una herramienta que puede controlarse desde la terminal. En lugar de editar el código cada vez que cambia una entrada, el usuario pasa nombres de archivos, formatos, límites, opciones y subcomandos al ejecutar el programa. El módulo estándar argparse interpreta esos valores y genera ayuda y errores automáticamente.

En esta guía aprenderás a crear una CLI en Python con argparse utilizando argumentos posicionales, opciones, banderas booleanas, tipos, valores permitidos, subcomandos, validación, pruebas y empaquetado moderno con pyproject.toml.

Por qué utilizar argparse

Es posible leer sys.argv directamente, pero tendrías que implementar mensajes de ayuda, conversión de tipos, detección de valores ausentes, alias y errores de uso. Argparse reúne esas responsabilidades en una interfaz declarativa.

La documentación oficial de argparse explica acciones, subparsers, formateadores y validación. No necesitas instalar ningún paquete.

Crear una CLI mínima

Guarda este archivo como saludar.py:

import argparse

parser = argparse.ArgumentParser(
    description="Muestra un saludo personalizado."
)
parser.add_argument("nombre", help="Nombre de la persona")
args = parser.parse_args()

print(f"¡Hola, {args.nombre}!")

Ejecuta:

python saludar.py Ana

Usa python saludar.py --help para ver la ayuda generada.

Argumentos posicionales y opcionales

parser.add_argument("archivo")

Un argumento posicional se identifica por su lugar. Una opción comienza con una bandera:

parser.add_argument("-o", "--salida")

La forma larga mejora la claridad y la corta facilita el uso frecuente.

Convertir tipos

Todos los argumentos llegan inicialmente como texto. Usa type para convertirlos.

parser.add_argument(
    "--limite",
    type=int,
    default=10,
    help="Cantidad máxima de resultados",
)

Cuando el usuario escribe un valor incompatible, argparse muestra un error y termina con un código distinto de cero.

Rutas con pathlib.Path

from pathlib import Path

parser.add_argument("archivo_entrada", type=Path)

Así recibes un objeto Path listo para trabajar con archivos y carpetas.

Banderas booleanas

parser.add_argument(
    "-v",
    "--detallado",
    action="store_true",
    help="Muestra información adicional",
)

Sin la bandera, el valor es falso. Cuando aparece, es verdadero.

Para crear opciones como --color y --no-color:

parser.add_argument(
    "--color",
    action=argparse.BooleanOptionalAction,
    default=True,
)

Restringir valores con choices

parser.add_argument(
    "--formato",
    choices=["texto", "json", "csv"],
    default="texto",
)

Los valores aceptados aparecen en la ayuda y cualquier otro se rechaza automáticamente.

Aceptar varios valores

parser.add_argument(
    "archivos",
    nargs="+",
    type=Path,
    help="Uno o más archivos",
)
  • ?: cero o un valor.
  • *: cero o más.
  • +: uno o más.
  • Un entero: cantidad exacta.

Repetir una opción

parser.add_argument(
    "--etiqueta",
    action="append",
    default=[],
)

Al ejecutar --etiqueta python --etiqueta cli obtienes una lista con dos valores.

Opciones mutuamente excluyentes

grupo = parser.add_mutually_exclusive_group()
grupo.add_argument("--silencioso", action="store_true")
grupo.add_argument("--detallado", action="store_true")

Argparse evita que ambas se utilicen al mismo tiempo.

Separar el parser de la lógica

def crear_parser():
    parser = argparse.ArgumentParser(
        description="Analiza un archivo de texto."
    )
    parser.add_argument("ruta", type=Path)
    parser.add_argument("--codificacion", default="utf-8")
    return parser


def main(argv=None):
    args = crear_parser().parse_args(argv)
    print(args.ruta, args.codificacion)

Permitir argv facilita pasar argumentos desde una prueba sin modificar sys.argv.

Validación personalizada

def entero_positivo(valor):
    try:
        numero = int(valor)
    except ValueError as error:
        raise argparse.ArgumentTypeError(
            f"{valor!r} no es un entero"
        ) from error

    if numero < 1:
        raise argparse.ArgumentTypeError(
            "el valor debe ser mayor o igual que 1"
        )

    return numero
parser.add_argument("--limite", type=entero_positivo)

Crear subcomandos

parser = argparse.ArgumentParser(prog="tareas")
subparsers = parser.add_subparsers(dest="comando", required=True)

agregar = subparsers.add_parser("agregar")
agregar.add_argument("titulo")

listar = subparsers.add_parser("listar")
listar.add_argument("--completadas", action="store_true")

eliminar = subparsers.add_parser("eliminar")
eliminar.add_argument("id_tarea", type=int)

Puedes asociar una función a cada subcomando:

def manejar_agregar(args):
    print(f"Agregando: {args.titulo}")


agregar.set_defaults(manejador=manejar_agregar)
args = parser.parse_args()
args.manejador(args)

Proyecto completo: estadísticas de archivos

import argparse
import json
import logging
from pathlib import Path


def entero_positivo(valor):
    try:
        numero = int(valor)
    except ValueError as error:
        raise argparse.ArgumentTypeError(
            f"{valor!r} no es un entero"
        ) from error

    if numero < 1:
        raise argparse.ArgumentTypeError(
            "el valor debe ser mayor o igual que 1"
        )

    return numero


def crear_parser():
    parser = argparse.ArgumentParser(
        prog="textstats",
        description="Cuenta líneas, palabras y caracteres.",
    )
    parser.add_argument(
        "rutas",
        nargs="+",
        type=Path,
        help="Archivos o carpetas",
    )
    parser.add_argument(
        "-r",
        "--recursivo",
        action="store_true",
    )
    parser.add_argument(
        "--patron",
        default="*.txt",
    )
    parser.add_argument(
        "--codificacion",
        default="utf-8",
    )
    parser.add_argument(
        "--formato",
        choices=["texto", "json"],
        default="texto",
    )
    parser.add_argument(
        "--limite",
        type=entero_positivo,
    )
    parser.add_argument(
        "-v",
        "--detallado",
        action="store_true",
    )
    parser.add_argument(
        "--version",
        action="version",
        version="%(prog)s 1.0.0",
    )
    return parser


def recopilar_archivos(rutas, patron, recursivo):
    archivos = []

    for ruta in rutas:
        if ruta.is_file():
            archivos.append(ruta)
            continue

        if ruta.is_dir():
            iterador = ruta.rglob(patron) if recursivo else ruta.glob(patron)
            archivos.extend(item for item in iterador if item.is_file())
            continue

        logging.warning("Ruta no encontrada: %s", ruta)

    return sorted(set(archivos))


def analizar_archivo(ruta, codificacion):
    texto = ruta.read_text(
        encoding=codificacion,
        errors="replace",
    )
    return {
        "ruta": str(ruta),
        "lineas": len(texto.splitlines()),
        "palabras": len(texto.split()),
        "caracteres": len(texto),
    }


def main(argv=None):
    args = crear_parser().parse_args(argv)
    logging.basicConfig(
        level=logging.INFO if args.detallado else logging.WARNING,
        format="%(levelname)s: %(message)s",
    )

    archivos = recopilar_archivos(
        args.rutas,
        args.patron,
        args.recursivo,
    )

    if args.limite is not None:
        archivos = archivos[: args.limite]

    if not archivos:
        raise SystemExit("No se encontraron archivos.")

    resultados = [
        analizar_archivo(ruta, args.codificacion)
        for ruta in archivos
    ]

    if args.formato == "json":
        print(json.dumps(resultados, indent=2, ensure_ascii=False))
    else:
        for resultado in resultados:
            print(resultado["ruta"])
            print(f"  líneas: {resultado['lineas']}")
            print(f"  palabras: {resultado['palabras']}")
            print(f"  caracteres: {resultado['caracteres']}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Ejemplos:

python textstats.py notas.txt
python textstats.py documentos --recursivo
python textstats.py documentos -r --formato json --limite 20
python textstats.py --help

Probar el parser

from pathlib import Path
from textstats import crear_parser


def test_parser():
    args = crear_parser().parse_args(
        ["notas.txt", "--formato", "json", "--limite", "5"]
    )

    assert args.rutas == [Path("notas.txt")]
    assert args.formato == "json"
    assert args.limite == 5

Empaquetar la CLI

[project]
name = "textstats-cli"
version = "0.1.0"
description = "Estadísticas de archivos de texto"
requires-python = ">=3.10"

[project.scripts]
textstats = "textstats.cli:main"

Después de instalar el paquete, el entorno crea el comando textstats.

Errores frecuentes

  • Interpretar argumentos al importar el módulo.
  • Usar type=bool para banderas.
  • Colocar toda la lógica justo después de parse_args().
  • No escribir ayuda útil.
  • Trabajar con rutas como texto cuando Path sería más claro.
  • Probar solamente argumentos válidos.

Conclusión

Una CLI en Python con argparse obtiene ayuda automática, tipos, opciones, banderas y subcomandos sin añadir dependencias. El diseño más mantenible construye el parser en una función, mantiene la lógica separada y permite pasar argumentos a main(argv=None) durante las pruebas.

Lecturas relacionadas

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Comparando listas e diferenças entre arrays em Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo comparar dos listas en Python y encontrar diferencias

    Compara listas en Python con igualdad, sorted, sets y Counter, conserva duplicados, detecta registros modificados y evita errores frecuentes.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Automação em Python para organizar arquivos do computador
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo organizar automáticamente los archivos de tu PC con Python

    Organiza automáticamente archivos del PC con Python, pathlib y shutil, evitando sobrescrituras, descargas incompletas y movimientos sin registro.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Script Python configurado para iniciar junto com o Windows
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo ejecutar un script de Python al iniciar Windows

    Configura un script Python para iniciar con Windows usando la carpeta Inicio, archivos BAT, Task Scheduler, entornos virtuales, logs y

    Ler mais

    Tempo de leitura: 6 minutos
    11/07/2026
    Sistema de backup automático de arquivos usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar copias de seguridad con Python

    Aprende a automatizar copias de seguridad con Python usando pathlib, shutil, fechas, verificación, registros y una estrategia segura de restauración.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Execução de comandos do terminal usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Subprocess en Python: ejecutar comandos del terminal

    Aprende subprocess en Python para ejecutar comandos, capturar salida, manejar errores, establecer timeouts y evitar riesgos con shell=True.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    representação de automação com ícones de engrenagens, gráficos e pessoas no background
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar tareas cotidianas con Python

    Aprende a automatizar tareas con Python: organizar archivos, renombrar lotes, procesar CSV, crear backups, usar logs y programar scripts.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026