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

ExecutandoEmIntervalos

Receita: Executando Funções em Intervalos de Tempo

Em fevereiro de 2004 surgiu na lista python-brasil uma pergunta sobre como se pode executar uma determinada função de intervalos em intervalos, como a função setInterval do JavaScript. Uma primeira solução utilizando apenas funções e timers foi proposta por Jonas Galvez, uma segunda foi proposta por mim (FelipeLessa) e uma outra terceira foi achada também por Jonas. Cada solução tem suas vantagens e desvantagens, veja cada uma e tire sua própria conclusão quanto preferências de implementação e aplicabilidade no seu código. Atualização (02/07/04): E agora temos uma quarta solução, usando a API do Twisted.

Primeira solução -- usando funções

Descrição

Essa é a primeira solução proposta. Basicamente uma função geradora cria uma outra que possui dentro de si um Timer() que, ao invés de chamar a função que foi solicitada, chama a si própria, recriando o Timer() que foi destruído e executando a função. Uma outra função acessa um valor interno da função que está rodando o Timer() e a instrui para parar o loop.

Vantagens e desvantagens

A vantagem clara é a simplicidade do código e de como ele é mantido, porém essa solução tem um ponto fraco: os ciclos de CPU e os recursos de sistema que são gastos com a criação e a destruição contínua dos objetos Timer(). É claro que essa diferença pode não ser percebida em intervalos grandes como o dos exemplos abaixo, porém em intervalos de décimos ou centésimos de segundo essa pode ser uma grande diferença.

Código

   1 from threading import Timer
   2 
   3 def setInterval(function, interval, *params, **kwparams):
   4     def setTimer(wrapper):
   5         wrapper.timer = Timer(interval, wrapper)
   6         wrapper.timer.start()
   7 
   8     def wrapper():
   9         function(*params, **kwparams)
  10         setTimer(wrapper)
  11     
  12     setTimer(wrapper)
  13     return wrapper
  14 
  15 def clearInterval(wrapper):
  16     wrapper.timer.cancel()

Exemplo de uso

   1 # Nos exemplos assumirei que o módulo "cookbook" contém todos os códigos
   2 from cookbook import setInterval
   3 from os.path import getsize
   4 
   5 FILE = '/var/log/syslog'
   6 
   7 def monitor(lastsize = [-1])
   8     size = getsize(FILE)
   9     if size <> lastsize[0]:
  10         print 'O tamanho do arquivo agora é %d kilobytes (%d bytes)' % \
  11               (round(size / 1024.0), size)
  12         lastsize[0] = size
  13 
  14 if __name__ == '__main__':
  15     print 'Checando a cada um minuto o tamanho do arquivo "%s".' % FILE
  16     print 'Atenção: esse programa NUNCA termina.'
  17     interval_monitor = setInterval(monitor, 60)
  18     while True:
  19         pass

Segunda solução -- usando classes e sleep

Descrição

Essa é a segunda solução proposta por FelipeLessa também na lista python-brasil. Ao invés de um Timer() ser recriado constantemente, a função time.sleep() é utilizada dentro de um laço while, dessa forma evitando a contrução/destruição de objetos constante.

Para os curiosos, as únicas diferenças entre o exemplo dessa solução e da primeira estão nas linhas 17, 18 e 19.

Vantagens e desvantagens

Essa implementação praticamente não gasta qualquer tipo de recurso entre uma execução e outra, tendo entre elas apenas a função time.sleep() e a checagem do laço while, dessa forma tendo praticamente nenhuma sobrecarga mesmo em intervalos curtos.

A única desvantagem é visual: há quem não goste de utilizar classes para esse tipo de tarefa.

Código

   1 from threading import Thread
   2 from time import sleep
   3 
   4 class IntervalRunner(Thread):
   5     def __init__(self, interval, function, *args, **kwargs):
   6         super(self, IntervalRunner).__init__(self)
   7 
   8         self.interval = interval
   9         self.function = function
  10         self.args = args
  11         self.kwargs = kwargs
  12         self.executing = False
  13 
  14     def run(self):
  15         self.executing = True
  16         while self.executing:
  17             self.function(*self.args, **self.kwargs)
  18             time.sleep(self.interval)
  19 
  20     def stop(self):
  21         self.executing = False

Exemplo de uso

   1 # Nos exemplos assumirei que o módulo "cookbook" contém todos os códigos
   2 from cookbook import IntervalRunner
   3 from os.path import getsize
   4 
   5 FILE = '/var/log/syslog'
   6 
   7 def monitor(lastsize = [-1])
   8     size = getsize(FILE)
   9     if size <> lastsize[0]:
  10         print 'O tamanho do arquivo agora é %d kilobytes (%d bytes)' % \
  11               (round(size / 1024.0), size)
  12         lastsize[0] = size
  13 
  14 if __name__ == '__main__':
  15     print 'Checando a cada um minuto o tamanho do arquivo "%s".' % FILE
  16     print 'Atenção: esse programa NUNCA termina.'
  17     interval_monitor = IntervalRunner(60, monitor)
  18     interval_monitor.start()
  19     interval_monitor.join() # Opcional, claro

Terceira solução -- usando classes e eventos

Descrição

Essa é a terceira solução proposta (e última, pelo menos enquanto eu escrevo :) ), originalmente postada no ASPN Python Cookbook. Para utilizá-la você precisa criar uma subclasse da mesma e o checador de atividade é implementado usando um objeto Event().

Vantagens & Desvantagens

Pode-se dizer que essa solução é uma irmã da segunda, filosofia igual e implementação diferente. Ao invés dela chamar uma função, ela já possui em si a função, o que significa que você deve criar uma subclasse. Essa é então a vantagem e a desvantagem. Se você precisa de um maior controle talvez goste dessa implementação, mas para tarefas simples é o mesmo que matar mosca com rifle de elefante.

Código

   1 import threading
   2 
   3 class TaskThread(threading.Thread):
   4     """Thread that executes a task every N seconds"""
   5 
   6     def __init__(self):
   7         threading.Thread.__init__(self)
   8         self._finished = threading.Event()
   9         self._interval = 15.0
  10 
  11     def setInterval(self, interval):
  12         """Set the number of seconds we sleep between executing our task"""
  13         self._interval = interval
  14 
  15     def shutdown(self):
  16         """Stop this thread"""
  17         self._finished.set()
  18 
  19     def run(self):
  20         while 1:
  21             if self._finished.isSet(): return
  22             self.task()
  23 
  24             # sleep for interval or until shutdown
  25             self._finished.wait(self._interval)
  26 
  27     def task(self):
  28         """The task done by this thread - override in subclasses"""
  29         pass

Exemplo de uso

   1 # Nos exemplos assumirei que o módulo "cookbook" contém todos os códigos
   2 from cookbook import TaskThread
   3 from os.path import getsize
   4 
   5 FILE = '/var/log/syslog'
   6 
   7 class FileSizeTracker(TaskThread):
   8     def __init__(self):
   9         super(self, FileSizeTracker).__init__(self)
  10         self._interval = 60.0 #segundos
  11         self._lastsize = -1
  12 
  13     def task(self):
  14         size = getsize(FILE)
  15         if size <> self._lastsize:
  16             print 'O tamanho do arquivo agora é %d kilobytes (%d bytes)' % \
  17                   (round(size / 1024.0), size)
  18             self._lastsize = size
  19 
  20 if __name__ == '__main__':
  21     print 'Checando a cada um minuto o tamanho do arquivo "%s".' % FILE
  22     print 'Atenção: esse programa NUNCA termina.'
  23     thread = FileSizeTracker()
  24     thread.start()
  25     thread.join() # Opcional, claro

Quarta solução -- usando o Twisted

Descrição

O Twisted Matrix é uma plataforma pra desenvolvimento de aplicações para a Internet. Obviamente não compensa usar uma biblioteca dessa magnitude simplesmente para executar uma função em determinados intervalos, porém, caso seu programa já utilize o Twisted, então dessa forma você vai poder continuar usando o loop principal do Twisted para todo seu programa. O exemplo é uma versão ligeiramente modificada da documentação do Twisted.

Devo lembrar que o Twisted não utiliza múltiplos threads ou processos, portanto não sofre dos problemas dos mesmos (a implementação do Python é a melhor possível).

Para maiores informações sobre a class LoopingCall do Twisted, veja a documentação referente.

Código

   1 # Nenhum. Tudo já foi feito =).

Exemplo de uso

   1 from twisted.internet import reactor, task
   2 from os.path import getsize
   3 
   4 FILE = '/var/log/syslog'
   5 
   6 def monitor(lastsize = [-1])
   7     size = getsize(FILE)
   8     if size <> lastsize[0]:
   9         print 'O tamanho do arquivo agora é %d kilobytes (%d bytes)' % \
  10               (round(size / 1024.0), size)
  11         lastsize[0] = size
  12 
  13 if __name__ == '__main__'
  14     print 'Checando a cada um minuto o tamanho do arquivo "%s".' % FILE
  15     print 'Atenção: esse programa NUNCA termina.'
  16     l = task.LoopingCall(monitor)
  17     l.start(1.0)
  18     reactor.run()

Conclusão Final

Basicamente pode-se criar o seguinte caminho de perguntas e respostas:

1) Você já usa o Twisted Matrix no seu programa?

  • Se sim, prefira a quarta solução.
  • Se não, vá para a próxima pergunta.

2) Você tem algo contra o uso de classes no código que usa o intervalo?

  • Se sim, utilize a primeira solução e tenha a certeza de que não vai ter a performance alterada.
  • Se não, vá para a pergunta 3.

3) Você precisa criar algum tipo de controle avançado da função que será executada ou precisa de outras funcionalidades que só podem ser obtidas usando uma classe (como multiple inheritance)?

  • Se sim, utilize a terceira solução. Com ela você cria a sua própria subclasse e pode reutilizá-la quantas vezes quiser.
  • Se não, utilize a segunda solução. Esta solução provê uma interface igual à primeira solução porém sem o gargalo que pode vir a existir nesta última.

Volta para CookBook.


FelipeLessa