sched no Python: agende eventos

Publicado em: 05/08/2026
Tempo de leitura: 8 minutos

Nem toda tarefa agendada precisa de um serviço externo, uma fila distribuída ou um cron do sistema. Para scripts, simuladores, testes e processos locais que precisam executar funções em horários definidos, o módulo sched no Python oferece um agendador pequeno e previsível dentro da biblioteca padrão. Ele organiza eventos por instante e prioridade, espera pelo momento adequado e chama funções com argumentos definidos pelo programa.

Neste guia, você aprenderá a criar um scheduler, usar enter() e enterabs(), cancelar eventos, executar em modo bloqueante ou não bloqueante e implementar repetições seguras. O conteúdo complementa nossos artigos sobre atexit no Python, tracebacks, contextvars, collections e diagnóstico de scripts lentos.

O que o módulo sched resolve

Um agendador recebe eventos e decide quando cada ação deve ser executada. Em sched, tudo acontece dentro do processo atual. Não há persistência automática, banco de dados, workers remotos ou recuperação após reinício.

Isso torna o módulo apropriado para:

  • executar callbacks depois de um atraso;
  • simular eventos em testes;
  • programar tarefas curtas dentro de uma ferramenta local;
  • organizar etapas temporizadas de uma demonstração;
  • criar um pequeno loop periódico sem dependências.

Para tarefas de produção que precisam sobreviver a crashes, distribuir carga ou executar em várias máquinas, use uma solução persistente e observável.

Criar um scheduler

A classe principal é sched.scheduler.

import sched

agendador = sched.scheduler()

Nas versões atuais, os argumentos padrão são time.monotonic para medir o tempo e time.sleep para esperar. Isso evita que ajustes no relógio civil atrasem ou antecipem eventos relativos.

A documentação oficial de sched informa que a classe pode ser usada com segurança em ambientes multithread desde Python 3.3, embora a lógica da aplicação ainda precise proteger seus próprios dados compartilhados.

Agendar depois de um atraso

enter() recebe um atraso em unidades da função de tempo, uma prioridade, uma ação, uma tupla de argumentos e, opcionalmente, argumentos nomeados.

import sched

agendador = sched.scheduler()


def avisar(mensagem):
    print(mensagem)

agendador.enter(
    delay=2,
    priority=1,
    action=avisar,
    argument=("Dois segundos se passaram",),
)

agendador.run()

Com as funções padrão, o atraso é expresso em segundos. O método devolve um objeto de evento que pode ser usado posteriormente para cancelamento.

Argumentos posicionais e nomeados

O parâmetro argument deve ser uma sequência, normalmente uma tupla. kwargs recebe um dicionário.

def registrar(nome, status="ok"):
    print(nome, status)

agendador.enter(
    1,
    1,
    registrar,
    argument=("importação",),
    kwargs={"status": "concluída"},
)

Evite usar objetos mutáveis que serão modificados antes da execução sem uma intenção clara. O callback receberá as referências armazenadas no evento.

Agendar em um instante absoluto

enterabs() agenda com base em um valor absoluto produzido pela função de tempo configurada.

import time

agendador = sched.scheduler(
    timefunc=time.monotonic,
    delayfunc=time.sleep,
)

instante = time.monotonic() + 5
agendador.enterabs(
    instante,
    1,
    avisar,
    argument=("Executado no prazo monotônico",),
)

Não misture valores de time.time() com um scheduler baseado em time.monotonic(). Os dois relógios possuem origens e finalidades diferentes.

Relógio monotônico versus relógio civil

time.monotonic() só avança e não é afetado por mudanças manuais, sincronização NTP ou horário de verão. É a melhor opção para atrasos e intervalos.

time.time() representa uma escala próxima do horário Unix e pode ser necessário quando o requisito é “às 15:00 de uma data”. Nesse caso, converta a data cuidadosamente e esteja preparado para alterações do relógio.

A documentação oficial de time explica as diferenças entre os relógios. Para calendários com fusos, considere datetime e zoneinfo antes de chegar ao scheduler.

Prioridade dos eventos

Quando dois eventos possuem o mesmo horário, a prioridade define a ordem. Números menores são executados primeiro.

agendador.enter(1, 20, avisar, argument=("prioridade 20",))
agendador.enter(1, 5, avisar, argument=("prioridade 5",))
agendador.enter(1, 10, avisar, argument=("prioridade 10",))

A prioridade não interrompe uma ação que já começou. Ela apenas organiza eventos que estão aguardando.

Executar a fila

run() processa eventos na ordem adequada.

agendador.run(blocking=True)

No modo bloqueante, o método espera até que todos os eventos existentes sejam executados. A função de atraso é chamada com o tempo restante antes de cada evento.

Eventos atrasados não são descartados

Se uma ação demora mais que o intervalo até o próximo evento, o scheduler fica atrasado. Ele executa os eventos vencidos assim que possível, mantendo a ordem, mas não descarta nenhum deles.

import time


def tarefa_lenta():
    time.sleep(3)
    print("tarefa lenta terminou")

agendador.enter(0, 1, tarefa_lenta)
agendador.enter(1, 1, avisar, argument=("evento atrasado",))
agendador.run()

Se o atraso acumulado é inaceitável, mova tarefas longas para workers controlados ou faça o callback apenas encaminhar trabalho.

Modo não bloqueante

Com blocking=False, run() executa os eventos já vencidos e retorna o prazo do próximo evento ou None quando a fila está vazia.

proximo = agendador.run(blocking=False)

if proximo is None:
    print("Fila vazia")
else:
    print("Próximo prazo:", proximo)

Esse modo permite integrar sched a outro loop. O valor retornado deve ser interpretado conforme a versão e a função de tempo; consulte a documentação da versão usada e calcule o atraso de forma consistente.

Integrar a um loop próprio

import time

while not agendador.empty():
    prazo = agendador.run(blocking=False)
    if prazo is None:
        break

    agora = time.monotonic()
    atraso = max(0, prazo - agora)
    processar_interface_por_ate(min(atraso, 0.1))

Um loop gráfico ou assíncrono provavelmente possui seu próprio mecanismo de timers. Nesses casos, adapte o prazo em vez de chamar sleep() na thread da interface.

Consultar a fila

A propriedade queue devolve os eventos pendentes em ordem de execução.

for evento in agendador.queue:
    print(
        evento.time,
        evento.priority,
        evento.action,
        evento.argument,
        evento.kwargs,
    )

A lista devolvida representa um snapshot ordenado. Alterar essa lista não muda a fila interna.

Verificar se a fila está vazia

empty() indica se existem eventos pendentes.

if agendador.empty():
    print("Nada agendado")

Em ambiente multithread, o estado pode mudar logo após a consulta. Não use empty() como garantia duradoura sem coordenar as threads.

Cancelar um evento

Guarde o objeto retornado por enter() ou enterabs() e passe-o a cancel().

evento = agendador.enter(
    30,
    1,
    avisar,
    argument=("não será executado",),
)

agendador.cancel(evento)

Se o evento não está mais na fila, cancel() lança ValueError.

Cancelamento concorrente

try:
    agendador.cancel(evento)
except ValueError:
    print("O evento já executou ou foi cancelado")

Em outra thread, o evento pode começar entre a decisão de cancelar e a chamada. O callback deve ser capaz de verificar um estado de cancelamento próprio quando a operação exige garantia adicional.

Repetir uma tarefa

Sched não possui um tipo especial de tarefa recorrente. O callback agenda a próxima ocorrência.

def executar_periodicamente(intervalo):
    print("executando")
    agendador.enter(
        intervalo,
        1,
        executar_periodicamente,
        argument=(intervalo,),
    )

agendador.enter(0, 1, executar_periodicamente, argument=(5,))

Esse modelo mede o intervalo a partir do momento do reagendamento. Se a função leva dois segundos, o início seguinte pode ocorrer sete segundos depois do anterior.

Evitar drift em repetições

Para manter uma grade absoluta, calcule o próximo instante a partir do prazo anterior.

def periodica(proximo, intervalo):
    print("execução periódica")
    seguinte = proximo + intervalo
    agendador.enterabs(
        seguinte,
        1,
        periodica,
        argument=(seguinte, intervalo),
    )

inicio = time.monotonic() + 1
agendador.enterabs(inicio, 1, periodica, argument=(inicio, 5))

Defina o que fazer quando várias ocorrências ficaram para trás: executar todas, pular algumas ou recalcular a próxima futura.

Exceções em callbacks

Se uma ação gera uma exceção, run() propaga o erro. O estado interno permanece consistente e o evento que falhou não é executado novamente automaticamente.

def protegido():
    try:
        operacao()
    except Exception:
        logger.exception("Evento agendado falhou")

agendador.enter(1, 1, protegido)

Escolha entre propagar e capturar conforme a responsabilidade da aplicação. Não esconda falhas críticas sem observabilidade.

Uso com threads

A fila do scheduler é thread-safe, então uma thread pode adicionar eventos enquanto outra executa run(). Seus callbacks e objetos compartilhados não recebem proteção automática.

from threading import Lock

lock = Lock()
estado = {}


def atualizar(chave, valor):
    with lock:
        estado[chave] = valor

Também considere que um run(blocking=True) já adormecido pode não acordar imediatamente quando outra thread insere um evento anterior, dependendo do padrão de integração. Loops responsivos devem usar verificações curtas ou um mecanismo de sinalização próprio.

Funções de tempo personalizadas

O construtor aceita funções diferentes, o que é excelente para testes.

class RelogioFalso:
    def __init__(self):
        self.agora = 0.0

    def tempo(self):
        return self.agora

    def esperar(self, segundos):
        self.agora += segundos

relogio = RelogioFalso()
agendador = sched.scheduler(relogio.tempo, relogio.esperar)

Assim, testes avançam virtualmente sem aguardar segundos reais.

delayfunc recebe zero

Depois de cada evento, o scheduler chama a função de atraso com zero para permitir que outras threads executem.

Uma delayfunc personalizada deve aceitar zero e não tratar esse valor como erro.

Testes determinísticos

eventos = []

agendador.enter(5, 1, eventos.append, argument=("A",))
agendador.enter(2, 1, eventos.append, argument=("B",))
agendador.run()

assert eventos == ["B", "A"]
assert relogio.agora == 5

Teste mesma hora com prioridades diferentes, cancelamento, exceção e eventos adicionados durante a execução.

Persistência e reinício

A fila vive apenas na memória. Ao encerrar o processo, todos os eventos pendentes desaparecem.

Para tarefas que devem sobreviver a reinícios, armazene-as em banco ou fila e recrie o scheduler na inicialização. Evite serializar callables arbitrários com pickle.

Várias máquinas

Sched não coordena locks distribuídos nem impede execução duplicada em duas instâncias. Cada processo possui sua própria fila.

Use um scheduler distribuído, banco com leasing ou fila de tarefas quando apenas uma instância deve executar o trabalho.

Observabilidade

Registre ao menos:

  • identificador lógico do evento;
  • horário planejado e real;
  • atraso acumulado;
  • duração;
  • status e exceção;
  • quantidade de eventos pendentes.

Não registre argumentos que contenham tokens, senhas ou dados pessoais.

Encerramento controlado

Use uma flag para impedir novos reagendamentos, cancele eventos pendentes quando apropriado e finalize callbacks em execução.

encerrando = False


def recorrente(intervalo):
    executar()
    if not encerrando:
        agendador.enter(intervalo, 1, recorrente, argument=(intervalo,))

Combine o protocolo com sinais e ciclo de vida da aplicação. atexit pode registrar uma última métrica, mas não garante execução em crash.

Erros frequentes

  • Misturar time.time() e time.monotonic().
  • Executar callbacks longos na thread do scheduler.
  • Presumir que eventos atrasados serão descartados.
  • Cancelar sem tratar ValueError.
  • Criar repetição acumulando drift sem intenção.
  • Usar a fila em memória para tarefas críticas.
  • Compartilhar estado sem locks.
  • Tratar sched como scheduler distribuído.

Boas práticas

  • Use relógio monotônico para intervalos.
  • Mantenha callbacks curtos e observáveis.
  • Guarde os objetos de evento para cancelamento.
  • Defina política para atrasos e repetições.
  • Use relógio falso em testes.
  • Proteja dados compartilhados.
  • Persista tarefas que devem sobreviver.
  • Escolha outra solução para múltiplas máquinas.

Conclusão

O módulo sched no Python oferece uma fila temporal simples para executar funções por atraso ou instante absoluto. Ele ordena eventos por horário e prioridade, suporta cancelamento, permite inspeção da fila e funciona com relógios personalizados.

Seu valor está na simplicidade local. Quando callbacks são curtos, o relógio é escolhido corretamente e a aplicação define políticas de atraso, repetição e encerramento, sched resolve timers internos sem dependências. Persistência, distribuição e recuperação, porém, exigem uma infraestrutura diferente.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    atexit no Python: execute limpeza ao sair

    Aprenda atexit no Python para executar limpeza no encerramento, controlar ordem LIFO e evitar problemas com threads, sinais e exceções.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Monitor com código binário representando análise de opcodes pickle com pickletools no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickletools no Python: analise pickles

    Aprenda pickletools no Python para desmontar pickles, analisar opcodes e otimizar fluxos sem executar dados não confiáveis.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Código-fonte em tela representando análise com tokenize no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tokenize no Python: analise código-fonte

    Aprenda tokenize no Python para ler tokens, comentários, indentação, codificação, posições e reconstruir código-fonte com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Desenvolvedor trabalhando em automação de build e compilação de diretórios com compileall no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    compileall no Python: compile diretórios

    Aprenda compileall no Python para compilar diretórios, gerar pyc em paralelo, filtrar arquivos e controlar otimização e invalidação.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Monitor com código binário representando geração de arquivos pyc com py_compile no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    py_compile no Python: gere arquivos pyc

    Aprenda py_compile no Python para gerar arquivos pyc, validar sintaxe e controlar otimização e invalidação por timestamp ou hash.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Editor de código representando correção de tabs e espaços com tabnanny no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tabnanny no Python: corrija indentação

    Aprenda tabnanny no Python para detectar tabs e espaços ambíguos, verificar projetos e evitar TabError e IndentationError.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026