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

Você não tem permissão para executar esta ação.

Excluir mensagem

QtSignalEmPython

Uma implementação de QT Signals em Python

Muitas vezes nós precisamos tratar eventos em geral em nossas aplicações. O caso mais comum ocorre em GUIs, mas este não é um caso único. Em alguns casos, queremos simplesmente criar um Observer.

Por exemplo, poderíamos querer observar quando um atributo foi alterado e executar uma determinada ação. Ou ainda executar ações bem mais complexas. Poderíamos querer enviar o valor alterado para um arquivo, para a interface com o usuário e mandar por e-mail, tudo ao mesmo tempo. É aqui que o padrão de sinais e slots, criados com a biblioteca Qt (PyQt, em nosso caso), é extremamente útil.

Em PyQt, nós podemos utilizar os sinais-slots de um modo extremamente simples:

   1 import sys, qt # Eu gosto de namespaces.
   2 
   3 def digaOla():
   4         print 'Olá Mundo!'
   5 
   6 def main():
   7    app = qt.QApplication(sys.argv)
   8 
   9    helloButton = qt.QPushButton('Olá Mundo', None)
  10    app.setMainWidget(helloButton)
  11    helloButton.show()
  12    # Conecta um sinal a um slot.
  13    helloButton.connect(helloButton, qt.SIGNAL('clicked()'), digaOla) # (1)
  14 
  15    app.exec_loop()
  16 
  17 if __name__ == '__main__':
  18    main()

Aqui em (1) dizemos para a função digaOla() observar o nosso botão e executar caso ele seja clicado. Simples não? A grande sacada é que podemos conectar quantas funções quisermos, e observar uma série de propriedades do botão.

Se você ainda não conhece este modo de tratar eventos, procure informação a respeito pois com certeza faz a diferença.

Agora nós queremos reproduzir isto, certo? Por quê? Para que possamos utilizar com qualquer classe, e de um modo mais pythônico. Primeiro a receita, depois as explicações.

Código

   1 class Signal(object):
   2    def __init__(self):
   3       self.__slotList = []
   4 
   5    def __call__(self, *args, **kwds):
   6       for slot in self.__slotList:
   7          slot(*args, **kwds)
   8 
   9    def addSlot(self, slot):
  10       if not callable(slot):
  11          raise ValueError('slots must be callable')
  12       if not slot in self.__slotList:
  13          self.__slotList.append(slot)
  14 
  15    def delSlot(self, slot):
  16       try:
  17          self.__slotList.remove(slot)
  18       except ValueError:
  19          pass
  20 
  21    def isConnected(self, slot):
  22       return slot in self.__slotList
  23 
  24 def connect(signal, slot):
  25    signal.addSlot(slot)
  26 
  27 def disconnect(signal, slot):
  28    signal.delSlot(slot)

O que fazemos aqui é simplesmente definir um objeto que armazenará uma lista de ações que executará. Quando chamarmos um objeto Signal, ele chamará cada uma das ações conectadas a ele. Vamos ao exemplo de uso:

Exemplo de uso

   1 def showChange(obj, oldValue):
   2    print 'Valor alterado de %s para %s.' % (oldValue, obj.value)
   3 
   4 def showObject(obj, oldValue):
   5    print 'Objeto com valor alterado:', obj
   6 
   7 class MyClass(object):
   8    def __init__(self, value):
   9       self.__value = value
  10       # Define um sinal observável.
  11       self.valueChanged = Signal() # (1)
  12 
  13    def setValue(self, value):
  14       oldValue = self.__value
  15       self.__value = value
  16       # Emite um sinal informando o objeto e o valor antigo.
  17       self.valueChanged(self, oldValue) # (2)
  18 
  19    def getValue(self):
  20       return self.__value
  21 
  22    value = property(getValue, setValue)
  23 
  24 def main():
  25    myObj = MyClass(3)
  26    # Conecta sinais aos slots.
  27    connect(myObj.valueChanged, showChange) # (3)
  28    connect(myObj.valueChanged, showObject) # Simples, não?
  29 
  30    try:
  31       while True:
  32          value = raw_input('Novo valor: ')
  33          myObj.value = value
  34    except KeyboardInterrupt:
  35       pass
  36 
  37 if __name__ == '__main__':
  38         main()

Este programa irá ficar perguntando por novos valores, e os setará no objeto criado, até receber uma interrupção do teclado (em geral, Ctrl+C). A parte interessante é que existem duas funções monitorando mudanças no valor do atributo value.

Em (1) simplesmente definimos um sinal. Em (2) nós o emitimos, quando a propriedade value for alterada. A classe Signal fica responsável por chamar todos os slots que observam o sinal. Note como fica simples trasmitir informações para nossos observadores. Em (3), conectamos os observadores (slots) ao sinal observado.

Uma outra facilidade provida pela classe é poder verificar se um slot está conectado a um determinado sinal, ou em algum ponto desconectar a ação do sinal.

Lendo sobre o assunto, você poderá encontrar vários termos para os Slots, como Observers ou Observadores e Listeners ou Ouvintes.

Volta para CookBook.


JoaoPauloSilva