uso de geradores como notificador de progressão
-
06-07-2019 - |
Pergunta
Atualmente estou usando geradores como uma maneira rápida de obter o progresso de processos longos e estou me perguntando como isso é feito normalmente, pois não acho muito elegante...
Deixe-me explicar primeiro, eu tenho um módulo engine.py que faz algum processamento de vídeo (segmentação, subtração bg/fg, etc) que leva muito tempo (de segundos a vários minutos).
Eu uso este módulo a partir de uma GUI escrita em wxpython e um script de console.Quando observei como implementar caixas de diálogo de progresso no wxpython, vi que precisava obter de alguma forma um valor de progresso para atualizar minha caixa de diálogo, o que é pura lógica, você admite...Então decidi usar o número de quadros processados em minhas funções de mecanismo, produzir o número do quadro atual a cada 33 quadros e produzir Nenhum quando o processamento for concluído.
fazendo isso, aqui está o que parece:
dlg = wx.ProgressDialog("Movie processing", "Movie is being written...",
maximum = self.engine.endProcessingFrame,self.engine.startProcessingFrame,
parent=self,
style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_SMOOTH | wx.PD_CAN_ABORT)
state = self.engine.processMovie()
f = state.next()
while f != None:
c, s = dlg.Update(f, "Processing frame %d"%f)
if not c:break
f = state.next()
dlg.Destroy()
Isso funciona muito bem, não há absolutamente nenhuma perda de velocidade perceptível, mas eu gostaria de poder chamar a função processMovie() sem ter que lidar com geradores, se não quiser.
Por exemplo, meu script de console que usa o módulo do mecanismo não se importa com o progresso, eu poderia usá-lo, mas está destinado a ser executado em um ambiente onde não há exibição, então realmente não me importo com o progresso...
Alguém com outro design que eu criei?(usando threads, globais, processos, etc)
Deve haver um design em algum lugar que faça esse trabalho de forma limpa, eu acho :-)
Solução
Usar um gerador é bom para isso, mas o objetivo de usar geradores é para que você possa incorporar a sintaxe:
for f in self.engine.processMovie():
c, s = dlg.Update(f, "Processing frame %d"%f)
if not c: break
Se você não se importa com isso, você pode dizer:
for f in self.engine.processMovie(): pass
ou expor uma função (por exemplo.engine.processMovieFull) para fazer isso por você.
Você também pode usar um retorno de chamada simples:
def update_state(f):
c, s = dlg.Update(f, "Processing frame %d"%f)
return c
self.engine.processMovie(progress=update_state)
...mas isso não é tão bom se você deseja processar os dados aos poucos;os modelos de retorno de chamada preferem fazer todo o trabalho de uma vez – esse é o verdadeiro benefício dos geradores.
Outras dicas
Parece um caso perfeito para eventos.O processo envia um "evento de atualização de status" e qualquer pessoa que queira saber (neste caso, a caixa de diálogo) escuta esse evento.
Primeiro de tudo, se você usa um gerador, você também pode usá-lo como iterador:
state = self.engine.processMovie()
for f in state:
c, s = dlg.Update(f, "Processing frame %d"%f)
if not c:
break
dlg.Destroy()
E não ceda None
;pare de ceder quando terminar e saia da função;alternativamente aumentar StopIteration
.Esta é a maneira correta de encerrar a geração (e ao usar um for
loop, é necessário).
Fora isso, gosto da ideia.Na minha opinião, este é um uso muito válido de geradores.
Você pode querer fazer o 33
configurável (ou seja,aceitável para processMovie
como parâmetro);33 parece uma escolha arbitrária, e se você processar um filme de duas horas, não há necessidade de atualizar a barra de progresso a cada 33 quadros, eu acho.