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

    Leitura e escrita de arquivos TXT usando Python
    Fundamentos
    Foto de perfil de Leandro Hirt da Academify

    Cómo leer y escribir archivos de texto en Python

    Aprende a leer, escribir, añadir y procesar archivos de texto en Python con UTF-8, with, pathlib y manejo seguro de

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Pessoa usando tablet com caneta digital para planejar tarefas em checklist, representando organização, planejamento e produtividade digital.
    Fundamentos
    Foto de perfil de Leandro Hirt da Academify

    sort() vs sorted() en Python: diferencias y ejemplos

    Aprende las diferencias entre sort() y sorted() en Python, con claves personalizadas, orden descendente, varios criterios y errores comunes.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Uso de expressões regulares regex para manipulação de texto em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Regex en Python: guía de expresiones regulares

    Aprende regex en Python con search, fullmatch, findall, grupos, cuantificadores, sustituciones, flags, patrones compilados y ejemplos prácticos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Jogo da forca para iniciantes desenvolvido com Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un juego del ahorcado en Python

    Crea un juego del ahorcado en Python con palabras aleatorias, validación, letras repetidas, vidas, dibujos ASCII, pistas y opción de

    Ler mais

    Tempo de leitura: 3 minutos
    11/07/2026
    Uso da função enumerate em loops Python
    Fundamentos
    Foto de perfil de Leandro Hirt da Academify

    enumerate() en Python: bucles con índices

    Aprende enumerate() en Python para recorrer valores con índices, elegir el inicio, combinarlo con zip, procesar archivos y evitar contadores

    Ler mais

    Tempo de leitura: 3 minutos
    11/07/2026
    Quiz interativo no terminal desenvolvido com Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un quiz en Python para la terminal

    Crea un quiz en Python para la terminal con preguntas, validación, puntuación, respuestas mezcladas, repetición, JSON y pruebas.

    Ler mais

    Tempo de leitura: 3 minutos
    11/07/2026