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

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