associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

Revisão 6e 2007-10-08 18:03:04

Excluir mensagem

ModuloSched

TableOfContents

Por vezes, queremos executar uma determinada ação daqui a um determinado número de segundos, ou em algum momento específico. A biblioteca padrão de Python provê uma maneira de fazermos isso através do módulo sched.

A classe scheduler

Praticamente todas as funcionalidades do módulo sched são providas pela classe scheduler. As instâncias dessa classe armazenam eventos que devem ser executados no futuro. Uma vez que se solicite que a instância de scheduler execute os eventos, ela tomará o controle da execução do programa e esperará pelo momento adequado para executar os eventos.

A classe scheduler exige dois parâmetros para seu inicializador: uma função que, ao ser invocada sem parâmetros, retorne o timestamp corrente e uma função que, ao ser invocada com um parâmetro numérico n, pause a execução do programa (ou da thread) n segundos. O nome dos parâmetros é, respectivamente, timefunc e delayfunc. Na maior parte dos casos, basta passar como parâmetro, respectivamente, as funções time e sleep do módulo time.

   1 >>> from time import time, sleep
   2 >>> from sched import scheduler
   3 >>> sch = scheduler(timefunc=time, delayfunc=sleep)
   4 >>> 

Agendamento de eventos a partir do momento corrente

Um evento, para o módulo sched, é uma ação que deve ser executada em um momento futuro. Para agendar um evento para n segundos no futuro, utilizamos o método enter. Esse método espera quatro parãmetros:

*delay Número de segundos a se esperar para executar o evento; pode ser um número de ponto flutuante, de modo que se pode agendar eventos para subunidades de segundos. *priority A prioridade do evento; caso haja mais de um evento agendado para o mesmo momento, a prioridade determina a ordem de execução. Como em Unix, quanto menor o valor numérico da prioridade, maior a prioridade (isto é, um evento com prioridade 0 tem mais prioriade que um evento com prioridade 1). *action Função a ser executada após a espera especificada. *argument Tupla com parâmetros para a função action. Se a função não requerir nenhum parâmetro, deve-se passar uma tupla vazia.

O método enter retorna uma tupla representando o evento. Essa tupla possui quatro valores: o primeiro é o timestamp absoluto da execução do evento, um número de ponto flutuante correspondendo ao momento, em absoluto, da execução do evento; o segundo é a prioridade do evento; o terceiro é a função a ser executada e o quarto é a tupla de argumentos para a função a ser executada.

Para ver um exemplo, vamos definir a função print_time. Esta função imprimirá o tempo corrente:

   1 >>> from time import strftime
   2 >>> def print_time():
   3 ...     print 'Time: %s' % strftime("%Hh%Mmin%Ss")
   4 ... 
   5 >>> 

Para solicitar ao scheduler criado acima que imprima o tempo corrente daqui a, digamos, cinco segundos, basta agendar o evento assim:

   1 >>> event = sch.enter(5, 0, print_time, ())
   2 >>> event
   3 (1191845022.8951671, 0, <function print_time at 0xb7d1be64>, ())
   4 >>>

A prioridade é 0 arbitrariamente; qualquer valor poderia ter sido passado. Note que, como nenhum argumento é requerido pela função, passamos uma tupla vazia como quarto argumento para enter. Note também que enter retorna uma tupla, que referenciamos com a variável event. Essa tupla também pode ser muito útil, conforme veremos à frente.

Execução dos eventos agendados

Agendar um evento não é o suficiente para que ele seja executado; uma vez que tenhamos agendado eventos, temos de solicitar que o objeto scheduler entre em "modo de execução". Para fazer isto, basta invocar o método run, que não exige nenhum argumento. Esse método "toma" para si o fluxo de execução do programa; em outras palavras, a não ser que você esteja usando algum paralelismo (threads, processos etc.), seu programa não executará nada até o fim da execução do método run, exceto pelos próprios eventos agendados. Por sinal, o método run só vai se encerrar ao executar o último evento agendado. Por exemplo, eis o código e a saída da execução que fiz em minha máquina. Definimos uma função para que não houvesse perda de tempo entre o agendamento e a execução por causa de digitação. Note como o evento foi efetivamente executado cinco segundos depois:

   1 >>> def print_and_schedule():
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     print_time()
   4 ...     sch.enter(5, 0, print_time, ())
   5 ...     sch.run()
   6 ... 
   7 >>> print_and_schedule()
   8 Time: 09h05min11s
   9 Time: 09h05min16s
  10 >>>

É válido notar também que a contagem do tempo para a execução de eventos começa a partir do momento em que foram agendados, e não a partir do momento em que o método run foi executado. Deste modo, se agendamos um evento às 14h30min05s para daqui a dez segundos mas só invocamos o método run às 14h30min10s, o evento, ainda assim, será executado às 14h30min15s.

A função add_delay definida abaixo ajudará a demonstrar isso. Essa função primeiro imprime a hora corrente e depois agenda um evento para event_delay segundos depois. Após agendá-lo, a função esperará previous_delay segundos antes de invocar o método run do scheduler.

   1 >>> def add_delay(previous_delay, event_delay):
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     print_time()
   4 ...     sch.enter(event_delay, 0, print_time, ())
   5 ...     sleep(previous_delay)
   6 ...     sch.run()
   7 ...
   8 >>>

Vamos confirmar, então, que a contagem do tempo começa a partir do momento de agendamento, e não a partir do momento de invocação do método run:

   1 >>> add_delay(5, 10)
   2 Time: 09h09min22s
   3 Time: 09h09min32s
   4 >>>

A função foi invocada para agendar o evento de informar as horas para dez segundos no futuro, mas esperaria cinco segundos antes de invocar o método run. Apesar disso, pode-se notar que o evento foi executado exatamente dez segundos após seu agendamento.

Eventos atrasados e múltiplos eventos

O que ocorreria se invocássemos o método run depois de passado o tempo para a execução do evento? Nesse caso, o evento atrasado seria executado imediatamente após a invocação do método. No exemplo abaixo, a função add_delay agenda um evento para dez segundos no futuro, mas aguarda quinze segundos antes de invocar o método run. Quando o método é invocado, vê-se que o evento foi executado imediatamente, isto é, quinze segundos após seu agendamento, embora tivesse sido agendado para cinco segundos antes.

   1 >>> add_delay(15, 10)
   2 Time: 09h10min52s
   3 Time: 09h11min07s
   4 >>> 

Também é válido notar que é possível agendar vários eventos para um mesmo scheduler. Note que após a execução dos eventos únicos que fizemos acima, o método run sempre retornava; se vários eventos forem agendados, o método só retornará após a execução do último. Abaixo, definimos a função execute_three_times, que agenda três eventos, cada um separado do outro por dez segundos, e depois executa o método run. Note como a o método run (e, portanto, a função execute_three_times) não retorna até a execução do último evento.

   1 >>> def execute_three_times():
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     sch.enter(20, 0, print_time, ())
   4 ...     sch.enter(30, 0, print_time, ())
   5 ...     sch.enter(40, 0, print_time, ())
   6 ...     sch.run()
   7 ... 
   8 >>> execute_three_times()
   9 Time: 09h15min01s
  10 Time: 09h15min11s
  11 Time: 09h15min21s
  12 >>> 

Agendamento de eventos para momento absoluto

Além de ser possível agendar eventos para serem executados após um período de tempo, também é possível agendar eventos para um momento absoluto. Por exemplo, é possível agendar um evento para ocorrer exatamente às 16h15min12s, independentemente do momento do agendamento e sem a necessidade de se calcular manualmente o intervalo de espera. Para fazermos isso, utilizamos o método enderabs. Esse método espera também quatro argumentos, com uma única diferença: o argumento que no método enter seria o intervalo a se esperar, no método enterabs é o timestamp do momento em que o evento será executado, contando-se os segundos a partir do Epoch time. Para uma explicação de como obter esse timestamp a partir do dia, da hora, do minuto e do segundo, consulte a documentação sobre o módulo time. Veja o exemplo abaixo:

   1 >>> from time import mktime
   2 >>> print_time()
   3 Time: 09h17min25s
   4 >>> timetuple = (2007, 10, 8, 9, 19, 0, 0, 0, 0)
   5 >>> timestamp = mktime(timetuple)
   6 >>> sch = scheduler(timefunc=time, delayfunc=sleep)
   7 >>> sch.enterabs(timestamp, 0, print_time, ())
   8 (1191845940.0, 0, <function print_time at 0xb7d1be64>, ())
   9 >>> print_time()
  10 Time: 09h18min29s
  11 >>> sch.run()
  12 Time: 09h19min00s
  13 >>>

Assim como no caso do agendamento para depois de um período, se um evento for agendado para antes de a execução do método run ocorrer, o evento será executado assim que o método run for invocado:

   1 >>> print_time()
   2 Time: 09h19min52s
   3 >>> timetuple = (2007, 10, 8, 9, 18, 0, 0, 0, 0)
   4 >>> timestamp = mktime(timetuple)
   5 >>> sch.enterabs(timestamp, 0, print_time, ())
   6 (1191845880.0, 0, <function print_time at 0xb7d1be64>, ())
   7 >>> print_time()
   8 Time: 09h20min29s
   9 >>> sch.run()
  10 Time: 09h20min31s
  11 >>> 

Múltiplos eventos agendados para um mesmo momento

Se vários eventos estiverem agendados para um mesmo momento, a ordem de execução é definida pela prioridade. Eventos com número de prioridade menor são executados antes. Por exemplo, abaixo definimos três funções, high_priority, medium_priority e low_priority. Essas funções serão agendadas no mesmo scheduler para um mesmo momento com prioridades diferentes: high_priority será agendada com prioridade 0; medium_priority será agendada com prioridade 1 e low_priority será agendada com prioridade 2. Note como high_priority é executada antes das demais por ter menor número de prioridade, e low_priority é executada depois por ter o maior número de prioridade.

   1 >>> def hight_priority():
   2 ...     print 'Hight priority'
   3 ... 
   4 >>> def medium_priority():
   5 ...     print 'Medium priority'
   6 ... 
   7 >>> def low_priority():
   8 ...     print 'Low priority'
   9 ... 
  10 >>> def schedule_with_properties():
  11 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
  12 ...     # Agendar para daqui a quinze segundos
  13 ...     timestamp = time() + 15 
  14 ...     sch.enterabs(timestamp, 0, hight_priority, ())
  15 ...     sch.enterabs(timestamp, 1, medium_priority, ())
  16 ...     sch.enterabs(timestamp, 2, low_priority, ())
  17 ...     sch.run()
  18 ... 
  19 >>> schedule_with_properties()
  20 Hight priority
  21 Medium priority
  22 Low priority
  23 >>> 

Para garantir que a ordem de execução foi garantida pelos números de prioridade e não pela ordem de agendamento, definimos outra função, randomly_schedule_with_priorities, onde os agendamentos são feitos em ordem divergente da ordem das prioridades. O resultado confirma que quem define a ordem de execução de eventos agendados para o mesmo momento é definida pelas prioridades:

   1 >>> def randomly_schedule_with_properties():
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     timestamp = time() + 15
   4 ...     sch.enterabs(timestamp, 2, low_priority, ())
   5 ...     sch.enterabs(timestamp, 1, medium_priority, ())
   6 ...     sch.enterabs(timestamp, 0, hight_priority, ())
   7 ...     sch.run()
   8 ... 
   9 >>> randomly_schedule_with_properties()
  10 Hight priority
  11 Medium priority
  12 Low priority
  13 >>> 

Caso vários eventos estejam agendados para um mesmo momento com uma mesma prioridade, a ordem de execução é, aparentemente, aleatória:

   1 >>> def schedule_without_properties():
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     timestamp = time() + 15
   4 ...     sch.enterabs(timestamp, 0, hight_priority, ())
   5 ...     sch.enterabs(timestamp, 0, medium_priority, ())
   6 ...     sch.enterabs(timestamp, 0, low_priority, ())
   7 ...     sch.run()
   8 ... 
   9 >>> schedule_without_properties()
  10 Medium priority
  11 Hight priority
  12 Low priority
  13 >>> 

Cancelamento de eventos

É possível cancelar um evento. Para fazer isso, utilizamos o método cancel. Esse método espera como argumento a tupla retornada pela criação do evento. No exemplo abaixo, definimos uma função schedule_and_cancel que agenda três eventos, cada um separado do outro por cinco segundos, mas cancela o segundo agendado. É fácil perceber que apenas o segundo evento não é executado: existe um salto de dez segundos entre o primeiro e o último, e o evento cuja função é second_event não é executado.

   1 >>> def first_event():
   2 ...     print '[%s] First event' % strftime('%Hh%Mmin%Ss')
   3 ... 
   4 >>> def second_event():
   5 ...     print '[%s] Second event' % strftime('%Hh%Mmin%Ss')
   6 ... 
   7 >>> def third_event():
   8 ...     print '[%s] Third event' % strftime('%Hh%Mmin%Ss')
   9 ... 
  10 >>> def schedule_and_cancel():
  11 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
  12 ...     sch.enter(5, 0, first_event, ())
  13 ...     event = sch.enter(10, 0, second_event, ())
  14 ...     sch.enter(15, 0, third_event, ())
  15 ...     sch.cancel(event)
  16 ...     sch.run()
  17 ... 
  18 >>> schedule_and_cancel()
  19 [11h15min22s] First event
  20 [11h15min32s] Third event
  21 >>>

É importante ressaltar que tentar cancelar um evento que não exista, ou que já tenha sido executado, resulta em uma exceção:

   1 >>> event = sch.enter(5, 0, print_time, ())
   2 >>> sch.run()
   3 Time: 11h16min37s
   4 >>> sch.cancel(event)
   5 Traceback (most recent call last):
   6   File "<stdin>", line 1, in ?
   7   File "/usr/lib/python2.4/sched.py", line 70, in cancel
   8     self.queue.remove(event)
   9 ValueError: list.remove(x): x not in list
  10 >>>

Argumentos para eventos

Conforme afirmamos acima, o quarto argumento dos métodos enter e enterabs é um argumento a ser passado para a função que será executada pelo evento. Para ver como isso funciona, considere o código abaixo. Nós definimos uma função print_string que recebe como parâmetro uma string que será impressa. Agendamos um evento que recebe como argumento a string a ser executada. De modo análogo, definimos a função print_concat que recebe duas strings e imprime a concatenação das duas. Note como, no primeiro caso, tivemos de passar uma tupla unitária, não uma string única.

   1 >>> def print_string(s):
   2 ...     print '[%s] %s' % (strftime('%Hh%Mmin%Ss'), s)
   3 ... 
   4 >>> def print_concat(s1, s2):
   5 ...     print '[%s] %s %s' % (strftime('%Hh%Mmin%Ss'), s1, s2)
   6 ... 
   7 >>> def schedule_with_arguments():
   8 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   9 ...     sch.enter(10, 0, print_string, ('First event',))
  10 ...     sch.enter(15, 0, print_concat, ('Second', 'event'))
  11 ...     sch.run()
  12 ... 
  13 >>> schedule_with_arguments()
  14 [11h25min49s] First event
  15 [11h25min54s] Second event
  16 >>> 

Eventos que alteram o agendador

Uma funcionalidade do módulo sched que abre muitas possibilidades é a habilidade de se registrar eventos que alterem, eles mesmos, o agendador. Em outras palavras, é possível agendar eventos que agendem ou cancelem outros eventos. No exemplo abaixo, definimos quatro funções. A primeira função informa as horas e agenda a execução da quarta função para vinte segundos; a segunda, apenas imprime as horas; a terceira, informa as horas e cancela o evento que receberá como argumento; e a quarta função, assim como a segunda, apenas informa as horas.

   1 >>> def first_event(sch):
   2 ...     print '[%s] First event' % strftime('%Hh%Mmin%Ss')
   3 ...     sch.enter(20, 0, fourth_event, ())
   4 ... 
   5 >>> def second_event():
   6 ...     print '[%s] Second event' % strftime('%Hh%Mmin%Ss')
   7 ... 
   8 >>> def third_event(sch, event):
   9 ...     print '[%s] Third event' % strftime('%Hh%Mmin%Ss')
  10 ...     sch.cancel(event)
  11 ... 
  12 >>> def fourth_event():
  13 ...     print '[%s] Fourth event' % strftime('%Hh%Mmin%Ss')
  14 ... 
  15 >>> 

Definidas as funções, definimos a função schedule_update_events abaixo. Nessa função, agendamos um evento com a função first_event para cinco segundos a partir da invocação da função; agendamos um evento com a função second_event para quinze segundos depois, mas passamos o resultado desse agendamento como argumento para um evento com a função third_event, que será executada em dez segundos (isto é, antes de second_event). O que se espera é que o primeiro evento seja executado, de fato, em cinco segundos; o segundo, porém, não deve ser executado, pois o terceiro evento será executado em dez segundos e cancelará o segundo; já a quarta função será executada vinte e cinco segundos depois da invocação de schedule_update_events. Note como o agendador passado como argumento para o primeiro e terceiro eventos é o mesmo que executará as funções.

   1 >>> def schedule_update_events():
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     sch.enter(5, 0, first_event, (sch,))
   4 ...     event = sch.enter(15, 0, second_event, ())
   5 ...     sch.enter(10, 0, third_event, (sch, event))
   6 ...     sch.run()
   7 ... 
   8 >>> schedule_update_events()
   9 [11h33min43s] First event
  10 [11h33min48s] Third event
  11 [11h34min03s] Fourth event
  12 >>>

Algo interessate de se fazer com essa funcionalidade são funções que se agendam após sua execução. Isso é especialmente interessante para executar certas tarefas periodicamente, seja período fixo ou variável. Por exemplo, a função schedule_itself abaixo imprime as horas e agenda um novo evento para cinco segundos após sua execução. Como se pode ver pela saída, a execução da cadeia recursiva de eventos só foi interrompida quando enviamos um sinal de interrupção via teclado (isto é, o famigerado Control-C).

   1 >>> def schedule_itself():
   2 ...     print '[%s] Recursive event' % strftime('%Hh%Mmin%Ss')
   3 ...     sch.enter(5, 0, schedule_itself, ())
   4 ... 
   5 >>> sch.enter(5, 0, schedule_itself, ())
   6 (1191854542.6855791, 0, <function schedule_itself at 0xb7d255dc>, ())
   7 >>> sch.run()
   8 [11h42min22s] Recursive event
   9 [11h42min27s] Recursive event
  10 [11h42min32s] Recursive event
  11 [11h42min37s] Recursive event
  12 [11h42min42s] Recursive event
  13 [11h42min47s] Recursive event
  14 [11h42min52s] Recursive event
  15 [11h42min57s] Recursive event
  16 Traceback (most recent call last):
  17   File "<stdin>", line 1, in ?
  18   File "/usr/lib/python2.4/sched.py", line 102, in run
  19     self.delayfunc(time - now)
  20 KeyboardInterrupt
  21 >>> 

Limitações e erros comuns

sched é um módulo prático, simples e divertido. Provê uma funcionalidade ao mesmo tempo simples e poderosa, que, se fosse ser implementada cada vez que fosse necessária, demadaria um esforço imenso. Sua implementação e sua interface é simples e surpreendentemente poderosa, suportando dois modos de agendamento e prioridades. Entretanto, há certas limitações importantes em sched.

Falta de paralelismo

A mais evidente limitação é que o módulo sched não é naturalmente paralelizado. Ao rodar o método run da classe scheduler, o interpretador "trava", não executando mais nada após o comando. Se você deseja que o agendador rode paralelamente, sem tomar o controle do programa indefinidamente, terá de inserir você mesmo algum tipo de paralelismo (threads, processos, sinais etc.) no código. Ao mesmo tempo em que isso dá bastante flexibilidade na ferramenta que seria usada no paralelismo, simplifica a implementação e torna o código mais otimizado, também obriga o programador a implementar algo consideravelmente sofisticado de se codificar.

De modo análogo, os eventos não são executados paralelamente. Quando o objeto scheduler executa um evento, esse evento toma o controle do fluxo do programa indefinidamente. Se temos dois eventos agendados e um termina sua execução antes do momento de executar o outro, o segundo evento será adequadamente executado no momento esperado; entretanto, se o primeiro evento toma o tempo de execução até depois do momento de execução do segundo evento, o segundo evento será postergado.

O código abaixo demonstra isso. A função with_delay toma dez segundos do tempo de execução do programa. Agendamos with_delay para ser executada aos 5 segundos após a chamada do método run, e a função without_delay para ser executada após dez segundos de execuçaõ do método run. Entretanto, with_delay demorará dez segundos para devolver o controle do fluxo de programa ao método, de modo que without_delay só será executada após cinco segundos depois do momento planejado.

   1 >>> def with_delay():
   2 ...     print '[%s] Delayed event (before)' % strftime('%Hh%Mmin%Ss')
   3 ...     sleep(10)
   4 ...     print '[%s] Delayed event (after)' % strftime('%Hh%Mmin%Ss')
   5 ... 
   6 >>> def without_delay():
   7 ...     print '[%s] Event without delay' % strftime('%Hh%Mmin%Ss')
   8 ... 
   9 >>> def scheduled_delayed_event():
  10 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
  11 ...     sch.enter(5, 0, with_delay, ())
  12 ...     sch.enter(10, 0, without_delay, ())
  13 ...     sch.run()
  14 ... 
  15 >>> scheduled_delayed_event()
  16 [11h46min39s] Delayed event (before)
  17 [11h46min49s] Delayed event (after)
  18 [11h46min49s] Event without delay
  19 >>>

Pitfall sobre agendamento em seqüência

Um detalhe mais importante é o fato de que agendar vários eventos com o método enter não vai usar o mesmo timestamp inicial como base. Explicando melhor: quando você agenda um evento para, digamos, daqui a cinco segundos, e logo abaixo agenda outro para cinco segundos também, e abaixo agenda outro, entre a primeira chamada do método enter e a segunda chamada, assim como entre a segunda chamada e a terceira chamada, passaram-se algumas frações de segundos que tornam os momentos de início da contagem de tempo diferentes.

O exemplo abaixo deixará a situação mais clara. A exemplo do código da seção "Múltiplos eventos agendados para um mesmo momento", criamos uma função schedule_with_priorities_and_enter que agenda eventos que executam as funções low_priority, medium_priority e high_priority. Note como, na execução, as prioridades não alteram a ordem de execução definida pela ordem de agendamento.

   1 >>> def schedule_with_priorities_and_enter():
   2 ...     sch = scheduler(timefunc=time, delayfunc=sleep)
   3 ...     sch.enter(5, 2, low_priority, ())
   4 ...     sch.enter(5, 1, medium_priority, ())
   5 ...     sch.enter(5, 0, hight_priority, ())
   6 ...     sch.run()
   7 ... 
   8 >>> schedule_with_priorities_and_enter()
   9 Low priority
  10 Medium priority
  11 Hight priority
  12 >>> 

Uma solução para isso é fazer exatamente o que fizemos na seção sobre prioridades: "pegamos" o timestamp atual, somamos o período, em segundos, que queremos que se espere e agendamos com o método enterabs. Seria interessante, porém, um método que agendasse toda uma lista de eventos a partir de um timestamp único.

Essas limitações são características, porém, de uma implementação simples, pythônica. No geral, o paralelismo é algo que certamente não "cairia bem" no módulo sched, porque é complicado e ineficiente, enquanto scheduler é uma classe claramente planejada para possuir interface e semântia simples. Alguns métodos a mais não fariam mal à classe, porém.

Interface pouco sofisticada

Uma desvantagem menor - mais uma chatice que um bug em si - é a falta de um método que cancele todos os eventos de uma vez só, algo como um método cancel_all. Uma forma não documentada de se fazer isso é atribuir uma lista vazia ao campo queue do objeto scheduler; um método documentado, porém, seria uma maneira melhor de fazer isso.

   1 >>> sch.enter(10, 0, print_time, ())
   2 (1191854985.3028569, 0, <function print_time at 0xb7d1be64>, ())
   3 >>> sch.enter(15, 0, print_time, ())
   4 (1191854993.495039, 0, <function print_time at 0xb7d1be64>, ())
   5 >>> sch.enter(20, 0, print_time, ())
   6 (1191855000.425101, 0, <function print_time at 0xb7d1be64>, ())
   7 >>> sch.queue = []
   8 >>> sch.run()
   9 >>> 

Em verdade, há vários detalhes que poderiam facilitar o desenvolvimento e evitar pitfalls no uso do módulo sched. Por exemplo, dado que na maioria das vezes as funções timefunc e delayfunc são as funções time e sleep do módulo time, elas poderiam ser o valor padrão dos argumentos do inicializador da classe sched. Outra idéia interessante seria transformar os argumentos priority e arguments dos métodos enter e enterabs em argumentos opcionais: é bastante comum que não sejam necessários. De modo análogo, seria interessante que, se a função agendada só espera um parâmetro, o argumento arguments dos métodos enter pudesse ser um único valor, e não obrigatoriamente uma tupla, assim como ocorre com o operador de formatação de strings %.

Alternativas ao módulo sched

Existem algumas alternativas ao módulo sched. Se, por exemplo, você quiser apenas interromper a execução de um programa por algum tempo, é mais conveniente utilizar a função sleep do módulo time, por exemplo.

Uma ótima alternativa ao módulo sched é a função timeout_add do módulo gobject. Os eventos agendados por timeout_add são executados paralelamente ao fluxo de execução do programa, e exceções lançadas por esses eventos não interrompem o programa. A desvantagem evidente na função timeout_add é que ela depende de todo o framework GObject. Se você já usa GObject na sua aplicação, timeout_add é uma alternativa; se não usa nem pretende usar, dificilmente compensaria. Ademais, timeout_add não provê nenhuma interface para agendar eventos para um momento absoluto, como faz o método enterabs de scheduler.

Conclusão

O módulo sched é simples e poderoso. Utilizá-lo, porém, exige que se tenha consciência de suas limitações e mesmo de seu propósito, que é ser um módulo simples. A despeito de algumas pequenas desvantagens facilmente remediáveis, porém, é um módulo extremamente pythônico em sua simplicidade e poder.