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.