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

Diferenças para "ModuloSched"

Diferenças entre as versões de 7 e 10 (3 versões de distância)
Revisão 7e 2007-10-11 19:05:34
Tamanho: 27346
Comentário: Melhorando formatação
Revisão 10e 2008-09-26 14:06:22
Tamanho: 27307
Editor: localhost
Comentário: converted to 1.6 markup
Deleções são marcadas assim. Adições são marcadas assim.
Linha 1: Linha 1:
[[TableOfContents]] <<TableOfContents>>
Linha 15: Linha 15:
>>> 
}}}
>>>
}}}
Linha 19: Linha 18:

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.
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.
Linha 40: Linha 41:
... 
>>> 
}}}
...
>>>
}}}
Linha 53: Linha 53:
Linha 57: Linha 56:
Linha 67: Linha 65:
...  ...
Linha 73: Linha 71:

É 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. 
É 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.
Linha 89: Linha 86:
Linha 99: Linha 95:
Linha 103: Linha 98:
Linha 111: Linha 105:
>>> 
}}}
>>>
}}}
Linha 124: Linha 117:
...  ...
Linha 129: Linha 122:
>>> 
}}}
>>>
}}}
Linha 134: Linha 126:
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: 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:
Linha 167: Linha 160:
>>> 
}}}
>>>
}}}
Linha 171: Linha 163:

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.
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.
Linha 178: Linha 169:
...  ...
Linha 181: Linha 172:
...  ...
Linha 184: Linha 175:
...  ...
Linha 188: Linha 179:
... timestamp = time() + 15  ... timestamp = time() + 15
Linha 193: Linha 184:
...  ...
Linha 198: Linha 189:
>>> 
}}}

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:
>>>
}}}
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 o que define a ordem de execução de eventos agendados para o mesmo momento são as prioridades:
Linha 212: Linha 202:
...  ...
Linha 217: Linha 207:
>>> 
}}}
>>>
}}}
Linha 231: Linha 220:
...  ...
Linha 236: Linha 225:
>>> 
}}}
>>>
}}}
Linha 240: Linha 228:
Linha 247: Linha 234:
...  ...
Linha 250: Linha 237:
...  ...
Linha 253: Linha 240:
...  ...
Linha 261: Linha 248:
...  ...
Linha 267: Linha 254:
Linha 283: Linha 269:
Linha 285: Linha 270:

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.
Conforme afirmamos acima, o quarto argumento dos métodos '''enter''' e '''enterabs''' é uma tupla com argumentos a serem passados 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.
Linha 292: Linha 276:
...  ...
Linha 295: Linha 279:
...  ...
Linha 301: Linha 285:
...  ...
Linha 305: Linha 289:
>>> 
}}}
>>>
}}}
Linha 309: Linha 292:

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.
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.
Linha 318: Linha 299:
...  ...
Linha 321: Linha 302:
...  ...
Linha 325: Linha 306:
...  ...
Linha 328: Linha 309:
... 
>>> 
}}}

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_even'''t 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.
...
>>>
}}}
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.
Linha 342: Linha 322:
...  ...
Linha 349: Linha 329:
Linha 357: Linha 336:
...  ...
Linha 374: Linha 353:
>>> 
}}}
>>>
}}}
Linha 378: Linha 356:

'''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'''.
'''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 são simples e surpreendentemente poderosas, suportando dois modos de agendamento e prioridades. Entretanto, há certas limitações significativas em '''sched'''.
Linha 382: Linha 359:

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. 
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 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.
Linha 395: Linha 371:
...  ...
Linha 398: Linha 374:
...  ...
Linha 404: Linha 380:
...  ...
Linha 411: Linha 387:

=== 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. 
=== ''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, passarão algumas frações de segundos que tornam os momentos de início da contagem de tempo diferentes.
Linha 426: Linha 400:
...  ...
Linha 431: Linha 405:
>>> 
}}}
>>>
}}}
Linha 436: Linha 409:
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.
Linha 439: Linha 410:
Linha 452: Linha 422:
>>>
}}}

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 '''%'''.
>>>
}}}
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 '''schedulder'''. 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 '''%''' Outra solução interessante seria os argumentos para a função passada ao método '''enter''' serem parâmetros opcionais do próprio método '''enter''', como ocorre na função '''timeout_add''' do módulo '''gobject'''.

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ântica simples. Alguns métodos a mais não fariam mal à classe, porém.
Linha 458: Linha 429:
Linha 461: Linha 431:
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'''. Uma alternativa possível ao módulo '''sched''' é a função '''timeout_add''' do módulo '''gobject'''. 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'''.
Linha 464: Linha 434:

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 o que define a ordem de execução de eventos agendados para o mesmo momento são as 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 é uma tupla com argumentos a serem passados 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 são simples e surpreendentemente poderosas, suportando dois modos de agendamento e prioridades. Entretanto, há certas limitações significativas 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 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, passarão 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.

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 schedulder. 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 % Outra solução interessante seria os argumentos para a função passada ao método enter serem parâmetros opcionais do próprio método enter, como ocorre na função timeout_add do módulo gobject.

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ântica simples. Alguns métodos a mais não fariam mal à classe, porém.

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 alternativa possível ao módulo sched é a função timeout_add do módulo gobject. 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.