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

    Imagem ilustrativa de conteúdo Python para YouTube
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo descargar tus propios vídeos de YouTube con Python

    Descarga copias de tus propios vídeos o contenido autorizado con Python y yt-dlp, validación, historial, límites, FFmpeg y prácticas responsables.

    Ler mais

    Tempo de leitura: 6 minutos
    12/07/2026
    Automação de postagens no Twitter usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar publicaciones en X con Python y Tweepy

    Automatiza publicaciones en X con Tweepy, credenciales seguras, validación, deduplicación, programación, logs y prácticas responsables contra el spam.

    Ler mais

    Tempo de leitura: 5 minutos
    12/07/2026
    Geração e edição de planilhas Excel usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear y editar archivos Excel con Python

    Crea y edita Excel con Pandas y openpyxl: hojas, fórmulas, estilos, filtros, validación, gráficos, rutas seguras y manejo de archivos

    Ler mais

    Tempo de leitura: 4 minutos
    12/07/2026
    Gerando aplicativo APK Android com Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo generar un APK Android con Python, Kivy y Buildozer

    Genera un APK Android con Python, Kivy y Buildozer: configuración, permisos, recursos, compilación, pruebas, almacenamiento, firma y publicación.

    Ler mais

    Tempo de leitura: 6 minutos
    12/07/2026
    Web scraper de notícias em Python com envio para Telegram
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un scraper de noticias y enviarlas a Telegram con Python

    Crea un scraper de noticias con Requests y Beautiful Soup, elimina duplicados y envía titulares a Telegram de forma segura

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Compactação de arquivos ZIP usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo descomprimir archivos ZIP con Python de forma segura

    Descomprime archivos ZIP con Python de forma segura: valida rutas, evita Zip Slip, limita tamaños, verifica integridad y procesa archivos

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026