graphlib no Python: ordenação topológica

Publicado em: 27/08/2026
Tempo de leitura: 8 minutos
A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.

O módulo graphlib oferece uma implementação da classe TopologicalSorter, usada para ordenar tarefas que possuem dependências. Uma ordenação topológica organiza os nós de um grafo direcionado acíclico de modo que cada dependência apareça antes do item que depende dela. Esse padrão aparece em sistemas de build, pipelines de dados, migrações, instalação de pacotes, geração de relatórios e execução de jobs.

O módulo não é uma biblioteca geral de grafos. Ele não calcula caminhos mínimos, centralidade, componentes ou fluxos. Seu foco é representar dependências e produzir uma ordem válida, incluindo uma API incremental que permite executar vários nós prontos em paralelo.

Modelo de dependências

TopologicalSorter recebe um mapping em que cada chave é um nó e o valor é um iterable com os predecessores desse nó.

from graphlib import TopologicalSorter

grafo = {
    "testar": {"instalar"},
    "empacotar": {"testar"},
    "publicar": {"empacotar"},
    "instalar": set(),
}

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

A relação é “esta tarefa depende destas outras”. É comum inverter acidentalmente o sentido e obter uma sequência oposta ao esperado.

Nós hashable

Os nós precisam ser hashable, porque são usados em dicionários e sets. Strings, números, enums e tuplas imutáveis são opções naturais.

Objetos mutáveis com hash instável não devem ser utilizados. Se a tarefa possui muitos metadados, use um identificador imutável como nó e mantenha os detalhes em outro mapping.

Dependências sem chave explícita

Um predecessor citado nos valores não precisa aparecer originalmente como chave. O sorter o adiciona como nó sem predecessores conhecidos.

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

Nesse exemplo, build também pertence ao grafo. Apesar da conveniência, declarar todos os nós explicitamente melhora validação e documentação.

static_order

static_order() é a forma mais simples de obter uma ordem completa.

sorter = TopologicalSorter(grafo)
for tarefa in sorter.static_order():
    executar(tarefa)

O resultado é um iterator. Consuma-o uma única vez ou transforme em tupla quando precisar reutilizar a sequência.

Mais de uma ordem pode ser válida

Quando duas tarefas não dependem uma da outra, qualquer ordem relativa entre elas pode ser válida. Não escreva testes que exijam uma sequência total específica sem necessidade.

Valide as relações: para cada aresta, o predecessor deve aparecer antes do dependente. Quando a apresentação precisa ser determinística, ordene os nós prontos por uma chave estável fora do sorter.

Detecção de ciclos

Uma ordenação topológica só existe em grafos acíclicos. Quando há ciclo, o módulo lança CycleError.

from graphlib import CycleError, TopologicalSorter

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

Um ciclo significa que nenhuma das tarefas envolvidas pode começar sem outra do mesmo grupo.

Diagnóstico de ciclos

A exceção inclui informações que ajudam a identificar um ciclo, mas o formato não deve ser tratado como API de relatório completa. Para uma interface amigável, converta os nós em nomes e mostre uma cadeia clara.

Um grafo pode ter vários ciclos. Corrigir o primeiro pode revelar outros, portanto repita a validação após cada alteração.

Adicione nós incrementalmente

add(node, *predecessors) adiciona ou amplia dependências antes da preparação.

sorter = TopologicalSorter()
sorter.add("compilar", "gerar_codigo")
sorter.add("testar", "compilar")
sorter.add("publicar", "testar")

Chamadas repetidas para o mesmo nó unem os predecessores. Isso é útil quando plugins contribuem com dependências.

prepare

Para a API incremental, chame prepare() depois de montar o grafo. A preparação valida a estrutura e habilita get_ready() e done().

sorter.prepare()

Não adicione novas dependências depois que a execução foi preparada. Construa e valide toda a configuração primeiro.

get_ready

get_ready() devolve os nós cujos predecessores já foram marcados como concluídos.

prontas = sorter.get_ready()
for tarefa in prontas:
    iniciar(tarefa)

Os nós retornados passam a estar “em andamento”. Eles não serão devolvidos novamente; o coordenador precisa chamar done() quando terminarem.

done

done(*nodes) informa que tarefas anteriormente prontas foram concluídas.

sorter.done("gerar_codigo")
novas = sorter.get_ready()

Não marque como concluído um nó que nunca foi devolvido por get_ready(). A classe valida transições inválidas e gera erros.

is_active

is_active() informa se ainda há trabalho que pode progredir: nós prontos não obtidos ou nós em andamento que ainda podem ser concluídos.

while sorter.is_active():
    for tarefa in sorter.get_ready():
        executar(tarefa)
        sorter.done(tarefa)

Em execução paralela, tarefas terminadas chegam por uma fila de resultados.

Execução paralela

A API incremental permite enviar todos os nós prontos a workers, aguardar conclusões e liberar dependentes.

from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED

sorter.prepare()
ativos = {}

with ThreadPoolExecutor(max_workers=4) as executor:
    while sorter.is_active():
        for tarefa in sorter.get_ready():
            ativos[executor.submit(executar, tarefa)] = tarefa

        concluidos, _ = wait(ativos, return_when=FIRST_COMPLETED)
        for futuro in concluidos:
            tarefa = ativos.pop(futuro)
            futuro.result()
            sorter.done(tarefa)

O exemplo precisa de política adicional para falhas, cancelamento e shutdown.

Falhas de tarefas

Uma tarefa que falha não deve ser marcada como concluída automaticamente se seus dependentes exigem o resultado. O coordenador precisa decidir entre interromper o pipeline, marcar dependentes como bloqueados ou usar um resultado de fallback.

Registre o estado separadamente: concluída, falhou, cancelada e ignorada não são equivalentes.

Cancelamento

Ao cancelar uma execução, pare de enviar novos nós, solicite cancelamento cooperativo dos workers e aguarde um prazo. Não chame done() para tarefas incompletas apenas para esvaziar o sorter.

Se a aplicação quiser continuar com dependências opcionais, modele explicitamente essa regra fora do grafo obrigatório.

Dependências opcionais

TopologicalSorter representa precedência obrigatória. Dependência opcional, condição de feature flag ou branch de ambiente exige uma etapa anterior que construa o grafo efetivo.

Remova nós desativados e ajuste dependências antes de chamar prepare().

Validação de referências

Como predecessores não declarados são adicionados automaticamente, um erro de digitação pode criar uma tarefa fantasma.

declaradas = set(configuracao)
referenciadas = set().union(*configuracao.values())
desconhecidas = referenciadas - declaradas

Decida se nós implícitos são permitidos. Em pipelines configurados por usuários, geralmente é melhor rejeitar nomes desconhecidos.

Representação de tarefas

Separe a identidade da função executora.

tarefas = {
    "extrair": lambda: extrair(dados),
    "transformar": transformar,
    "carregar": carregar,
}

Isso mantém o grafo serializável, facilita logs e permite validar nomes sem executar código.

Side effects e idempotência

Uma ordem válida não protege contra efeitos parciais. Se uma tarefa grava arquivo, banco ou API, defina idempotência, transação e rollback.

Ao retomar um pipeline, não presuma que uma tarefa com log de início terminou corretamente. Grave checkpoints após sucesso verificável.

Prioridades

O módulo não implementa prioridade entre nós simultaneamente prontos. O coordenador pode ordenar a tupla de get_ready() antes do envio.

for tarefa in sorted(sorter.get_ready(), key=prioridade):
    iniciar(tarefa)

A prioridade não pode violar dependências; ela só escolhe entre opções já liberadas.

Limite de concorrência

Nem todo nó pronto deve iniciar imediatamente. Respeite o número de workers, conexões de banco, memória e rate limits.

Mantenha uma fila local de prontos ou envie apenas até a capacidade disponível. O artigo sobre concurrent.futures no Python explica backpressure e pools.

Recursos por categoria

Algumas tarefas usam CPU, outras banco ou rede. Um único limite global pode ser inadequado. Mantenha semáforos ou executors separados por tipo de recurso.

O grafo define precedência, enquanto o scheduler define capacidade.

Persistência do estado

TopologicalSorter não é um motor durável. Se o processo reiniciar, o estado em memória é perdido.

Para pipelines críticos, persista o grafo, tentativas, outputs, timestamps e status em armazenamento transacional. Reconstrua o sorter apenas para calcular quais tarefas podem avançar.

Grafos grandes

A ordenação possui custo proporcional ao número de nós e arestas em condições normais, mas memória e representação dos sets importam. Evite duplicar grandes estruturas sem necessidade.

Use IDs compactos e valide limites antes de aceitar configurações externas.

Entrada não confiável

Um usuário pode enviar um grafo enorme, com muitos predecessores ou nomes gigantes, causando consumo de memória e CPU.

Limite nós, arestas, tamanho de identificadores e tempo de preparação. Não associe nomes diretamente a imports ou comandos sem uma allowlist.

Visualização

O módulo não gera diagramas. Para depuração, exporte arestas para DOT, JSON ou uma tabela. Destaque ciclos, tarefas bloqueadas e nós sem dependentes.

Uma visualização é especialmente útil quando plugins adicionam relações automaticamente.

Testes

Teste grafo vazio, um nó, cadeia linear, branches independentes, convergência, múltiplas ordens válidas, ciclo, referência desconhecida, falha de tarefa, cancelamento e execução paralela.

Em testes de ordem, verifique precedência em vez de comparar uma lista total quando houver liberdade.

Erros comuns

Os erros mais frequentes são inverter o sentido das dependências, exigir uma única ordem, não chamar done(), marcar tarefa falha como concluída, adicionar nós depois de preparar, aceitar typo como nó implícito, iniciar trabalho ilimitado e tratar o sorter como sistema persistente de workflow.

Conclusão

graphlib.TopologicalSorter resolve de forma direta a ordenação de tarefas com dependências e oferece uma API incremental adequada a schedulers paralelos. Modele predecessores corretamente, valide ciclos e nomes, limite concorrência e mantenha o estado de execução separado do grafo.

Consulte a documentação oficial de graphlib e o guia de multiprocessing no Python quando as tarefas exigirem processos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    weakref: evite reter objetos em caches

    Aprenda weakref no Python para referências fracas, caches, WeakSet, WeakMethod, finalize, callbacks e prevenção de retenção acidental.

    Ler mais

    Tempo de leitura: 8 minutos
    27/08/2026
    Young professional woman working on a laptop in an office setting, concentrating on her task.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextlib no Python: gerencie recursos

    Aprenda contextlib no Python com contextmanager, ExitStack, suppress, closing, asynccontextmanager e cleanup seguro de recursos.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Vivid close-up of code on a computer screen showcasing programming details.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ast no Python: analise código-fonte

    Aprenda ast no Python para analisar e transformar código, criar visitors, preservar posições, usar literal_eval e evitar riscos de execução.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Rustic exposed brick wall featuring aged electrical sockets and metal conduit.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    socket no Python: redes TCP e UDP

    Aprenda socket no Python para clientes e servidores TCP, UDP, framing, timeouts, IPv6, concorrência, TLS e segurança de rede.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    multiprocessing no Python: vários núcleos

    Aprenda multiprocessing no Python com processos, pools, filas, pipes, memória compartilhada, cancelamento, segurança e shutdown correto.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Monochrome image showcasing concentric circles in a tunnel-like structure creating a sense of depth.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.futures: threads e processos em paralelo

    Aprenda concurrent.futures no Python com threads, processos, Future, timeouts, cancelamento, backpressure e prevenção de deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026