graphlib en Python: orden topológico

Publicado el: 27/08/2026
Tempo de leitura: 7 minutos
Close-up view of a computer screen displaying code in a software development environment.

El módulo graphlib ofrece la clase TopologicalSorter para ordenar tareas que tienen dependencias. Un orden topológico organiza los nodos de un grafo dirigido acíclico de manera que cada requisito aparezca antes del elemento que depende de él. Este patrón aparece en sistemas de build, pipelines de datos, migraciones, instalación de paquetes, generación de informes y ejecución de jobs.

El módulo no es una biblioteca general de grafos. No calcula caminos mínimos, centralidad, componentes o flujos. Su objetivo es representar dependencias y producir un orden válido, además de una API incremental que permite ejecutar en paralelo varios nodos listos.

Modelo de dependencias

TopologicalSorter recibe un mapping donde cada clave es un nodo y el valor es un iterable con sus predecesores.

from graphlib import TopologicalSorter

grafo = {
    "probar": {"instalar"},
    "empaquetar": {"probar"},
    "publicar": {"empaquetar"},
    "instalar": set(),
}

orden = tuple(TopologicalSorter(grafo).static_order())
print(orden)

La relación significa “esta tarea depende de estos predecesores”. Invertir el sentido es un error frecuente.

Nodos hashable

Los nodos deben ser hashable porque se almacenan en diccionarios y sets. Strings, números, enums y tuplas inmutables son opciones naturales.

No uses objetos mutables con hash inestable. Mantén un identificador compacto e inmutable en el grafo y guarda los metadatos en otro mapping.

Predecesores implícitos

Un predecesor citado en los valores no necesita aparecer como clave explícita. El sorter lo añade como nodo sin predecesores conocidos.

grafo = {
    "deploy": {"build"},
}

Aquí build también forma parte del grafo. La comodidad es útil, pero declarar todos los nodos mejora validación y documentación.

static_order

static_order() es la forma más simple de producir un orden completo válido.

sorter = TopologicalSorter(grafo)
for tarea in sorter.static_order():
    ejecutar(tarea)

El resultado es un iterator. Consúmelo una vez o conviértelo en tupla si necesitas reutilizar la secuencia.

Puede haber varios órdenes válidos

Cuando dos tareas son independientes, cualquiera de los dos órdenes relativos satisface el grafo. Los tests no deberían exigir una secuencia total exacta salvo que la aplicación añada otra regla determinística.

Valida la precedencia: cada predecesor debe aparecer antes que su dependiente. Ordena los nodos listos por una clave estable cuando necesites presentación reproducible.

Detección de ciclos

Un orden topológico solo existe en un grafo acíclico. Un ciclo genera CycleError.

from graphlib import CycleError, TopologicalSorter

try:
    orden = tuple(TopologicalSorter({
        "a": {"b"},
        "b": {"a"},
    }).static_order())
except CycleError as error:
    print("ciclo detectado", error)

Un ciclo significa que ninguna de sus tareas puede comenzar porque cada una espera a otra del mismo grupo.

Informe de ciclos

La excepción contiene información útil para localizar un ciclo, pero no debe tratarse como una API completa de presentación. Convierte IDs en nombres legibles y muestra una cadena clara.

Un grafo puede tener varios ciclos. Corregir el primero puede revelar otro, así que vuelve a validar.

Añade nodos incrementalmente

add(node, *predecessors) añade o amplía dependencias antes de preparar.

sorter = TopologicalSorter()
sorter.add("compilar", "generar_codigo")
sorter.add("probar", "compilar")
sorter.add("publicar", "probar")

Llamadas repetidas para el mismo nodo unen los predecesores. Es útil cuando plugins contribuyen con relaciones.

prepare

La API incremental comienza con prepare(). La llamada valida el grafo y habilita get_ready() y done().

sorter.prepare()

No añadas nuevas dependencias después de preparar. Construye y valida toda la configuración primero.

get_ready

get_ready() devuelve los nodos cuyos predecesores ya fueron marcados como terminados.

listas = sorter.get_ready()
for tarea in listas:
    iniciar(tarea)

Los nodos devueltos pasan a estar “en progreso”. No volverán a aparecer; el coordinador debe llamar done() al terminar.

done

done(*nodes) marca como completos nodos previamente listos.

sorter.done("generar_codigo")
nuevas = sorter.get_ready()

No marques un nodo que nunca fue devuelto por get_ready(). Las transiciones inválidas generan errores.

is_active

is_active() indica si todavía puede haber progreso: nodos listos no obtenidos o nodos en ejecución que pueden concluir.

while sorter.is_active():
    for tarea in sorter.get_ready():
        ejecutar(tarea)
        sorter.done(tarea)

En ejecución paralela, las tareas concluidas suelen llegar por una queue o un conjunto de futures.

Ejecución paralela

La API incremental permite enviar todos los nodos listos a workers, esperar resultados y liberar dependientes.

from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED

sorter.prepare()
activas = {}

with ThreadPoolExecutor(max_workers=4) as executor:
    while sorter.is_active():
        for tarea in sorter.get_ready():
            activas[executor.submit(ejecutar, tarea)] = tarea

        concluidas, _ = wait(activas, return_when=FIRST_COMPLETED)
        for futuro in concluidas:
            tarea = activas.pop(futuro)
            futuro.result()
            sorter.done(tarea)

Una versión de producción también necesita políticas de error, cancelación y shutdown.

Fallos de tareas

Una tarea fallida no debe marcarse automáticamente como completa cuando sus dependientes requieren un resultado válido. El coordinador decide si detiene el pipeline, bloquea descendientes o usa un fallback documentado.

Mantén estados distintos: éxito, fallo, cancelación, skip y bloqueo no son equivalentes.

Cancelación

Al cancelar, deja de enviar nuevos nodos, solicita cancelación cooperativa y espera un plazo. No llames done() sobre tareas incompletas para vaciar el sorter.

Las rutas opcionales deben modelarse explícitamente fuera del grafo obligatorio.

Dependencias opcionales

TopologicalSorter representa precedencia obligatoria. Feature flags, condiciones de entorno y plugins opcionales requieren una etapa que construya el grafo efectivo antes de prepare().

Elimina nodos desactivados y ajusta relaciones antes de ejecutar.

Valida referencias

Como los predecesores ausentes se convierten en nodos implícitos, un typo puede crear una tarea fantasma.

declaradas = set(configuracion)
referenciadas = set().union(*configuracion.values())
desconocidas = referenciadas - declaradas

Decide si permites nodos implícitos. Los pipelines configurados por usuarios suelen beneficiarse de rechazar nombres desconocidos.

Separa identidad y ejecución

Guarda IDs de tareas en el grafo y funciones en otro mapping.

tareas = {
    "extraer": lambda: extraer(datos),
    "transformar": transformar,
    "cargar": cargar,
}

Esto mantiene el grafo serializable, mejora logs y evita ejecutar código durante validación.

Side effects e idempotencia

Un orden válido no protege frente a escrituras parciales. Las tareas que modifican archivos, bases o APIs necesitan idempotencia, transacciones y rollback.

Al reanudar un pipeline, no supongas que un log de inicio significa éxito. Persiste checkpoints después de un resultado verificable.

Prioridades

El módulo no prioriza nodos listos simultáneamente. El coordinador puede ordenar la tupla devuelta por get_ready().

for tarea in sorted(sorter.get_ready(), key=prioridad):
    iniciar(tarea)

La prioridad elige entre nodos liberados, pero no puede violar dependencias.

Límites de concurrencia

No todas las tareas listas deben comenzar inmediatamente. Respeta workers, pool de base, memoria, ancho de banda y rate limits.

Mantén una queue local o envía solo hasta la capacidad disponible. Consulta concurrent.futures en Python para backpressure.

Clases de recursos

Algunas tareas consumen CPU, otras base o red. Un único límite global puede ser inadecuado. Usa executors o semáforos separados por recurso.

El grafo describe precedencia; el scheduler describe capacidad.

Estado persistente

TopologicalSorter no es un motor durable. Un reinicio pierde el progreso en memoria.

Los pipelines críticos deben persistir grafo, intentos, outputs, timestamps y estados. Reconstruye el sorter solo para calcular qué tareas pueden avanzar.

Grafos grandes

La ordenación suele escalar con nodos y aristas, pero la representación y memoria importan. Evita duplicar estructuras grandes.

Usa IDs compactos y límites antes de aceptar configuración externa.

Entrada no confiable

Un usuario puede enviar millones de aristas, nombres enormes o fan-in extremo y consumir CPU y memoria.

Limita nodos, aristas, longitud de IDs y tiempo de preparación. No conviertas nombres directamente en imports o comandos sin allowlist.

Visualización

El módulo no genera diagramas. Exporta aristas a DOT, JSON o tabla. Destaca ciclos, tareas bloqueadas y nodos finales.

La visualización es especialmente útil cuando plugins añaden dependencias automáticamente.

Pruebas

Prueba grafo vacío, un nodo, cadena lineal, branches independientes, convergencia, varios órdenes válidos, ciclos, referencias desconocidas, fallos, cancelación y finalización paralela.

Cuando el orden no sea único, verifica restricciones de precedencia en vez de una lista exacta.

Errores comunes

Los fallos frecuentes son invertir dependencias, exigir un único orden, olvidar done(), marcar fallos como completos, añadir nodos tras preparar, aceptar typos como tareas, lanzar trabajo ilimitado y tratar el sorter como sistema persistente.

Conclusión

graphlib.TopologicalSorter resuelve la ordenación de dependencias y ofrece una interfaz incremental adecuada para schedulers paralelos. Modela correctamente los predecesores, valida ciclos y nombres, limita concurrencia y separa el estado de ejecución del grafo.

Consulta la documentación oficial de graphlib y multiprocessing en Python cuando las tareas necesiten procesos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    weakref: evita retener objetos en cachés

    Aprende weakref en Python para referencias débiles, caches, WeakSet, WeakMethod, finalize, callbacks y evitar retención accidental.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A top view of stacked timber logs showcasing natural textures and patterns.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextlib en Python: gestiona recursos

    Aprende contextlib en Python con contextmanager, ExitStack, suppress, closing, asynccontextmanager y cleanup seguro de recursos.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Close-up of a computer screen displaying colorful programming code with depth of field.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ast en Python: analiza código fuente

    Aprende ast en Python para analizar y transformar código, crear visitors, conservar posiciones, usar literal_eval y evitar riesgos de ejecución.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up of electric plug and socket with vibrant lighting, showcasing technology and energy concepts.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    socket en Python: redes TCP y UDP

    Aprende socket en Python para clientes y servidores TCP y UDP, framing, timeouts, IPv6, concurrencia, TLS y seguridad de red.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    multiprocessing en Python: varios núcleos

    Aprende multiprocessing en Python con procesos, pools, queues, pipes, memoria compartida, cancelación, seguridad y shutdown correcto.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Bright yellow and blue shopping carts arranged in orderly rows outdoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.futures: hilos y procesos en paralelo

    Aprende concurrent.futures en Python con threads, procesos, Future, timeouts, cancelación, backpressure y prevención de deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026