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

    Rack de servidores representando balanceamento de conexões com SO_REUSEPORT_LB no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: distribua conexões entre workers

    Aprenda SO_REUSEPORT_LB no Python para distribuir conexões entre múltiplos workers com segurança, testes e portabilidade.

    Ler mais

    Tempo de leitura: 6 minutos
    11/10/2026
    Código Python assíncrono em notebook para inspect.markcoroutinefunction
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: identifique wrappers async

    Aprenda inspect.markcoroutinefunction no Python para identificar wrappers assíncronos, integrar frameworks e evitar detecção incorreta de corrotinas.

    Ler mais

    Tempo de leitura: 6 minutos
    10/10/2026
    Código Python para percorrer pastas e arquivos com Path.walk
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: percorra diretórios com segurança

    Aprenda Path.walk no Python para percorrer diretórios, filtrar arquivos, tratar erros e controlar a travessia com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    10/10/2026
    Depuração de processo Python em terminal com código
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: depure processos Python em execução

    Aprenda a anexar o pdb a um processo Python em execução, inspecionar pilhas e diagnosticar travamentos com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Código Python e representação de frações numéricas
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: converta números em frações

    Aprenda fractions.from_number no Python para converter números em frações exatas, controlar precisão e evitar arredondamentos inesperados.

    Ler mais

    Tempo de leitura: 5 minutos
    09/10/2026
    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026