2011-01-02 6 views
7

Sto cercando di creare una calcolatrice con PyQt4 e il collegamento dei segnali "clicked()" dai pulsanti non è come previsto. Sto creando i miei pulsanti per i numeri all'interno di un ciclo for in cui provo a collegarli in seguito.Connessione di slot e segnali in PyQt4 in un loop

def __init__(self):  
    for i in range(0,10): 
     self._numberButtons += [QPushButton(str(i), self)] 
     self.connect(self._numberButtons[i], SIGNAL('clicked()'), lambda : self._number(i)) 

def _number(self, x): 
    print(x) 

Quando faccio clic sui pulsanti, tutti loro stampano '9'. Perché è così e come posso risolvere questo problema?

risposta

12

Questo è solo, come scope, nome ricerca e chiusure sono definiti in Python.

Python introduce solo nuovi collegamenti nello spazio dei nomi attraverso l'assegnazione e tramite gli elenchi di parametri delle funzioni. i non è quindi effettivamente definito nello spazio dei nomi di lambda, ma nello spazio dei nomi di __init__(). La ricerca del nome per i nella lambda termina quindi nello spazio dei nomi di __init__(), dove viene infine associato a 9. Questo è chiamato "chiusura".

È possibile aggirare questa semantica dichiaratamente non intuitiva (ma ben definita) passando i come argomento di parole chiave con valore predefinito. Come detto, i nomi nelle liste di parametri introdurre nuovi attacchi nello spazio dei nomi locale, in modo i all'interno del lambda diventa quindi indipendente dal i in .__init__():

self._numberButtons[i].clicked.connect(lambda i=i: self._number(i)) 

Una più leggibile, un'alternativa meno la magia è functools.partial:

self._numberButtons[i].clicked.connect(partial(self._number, i)) 

Sto usando la sintassi del segnale e dello slot di nuovo stile qui semplicemente per comodità, la sintassi vecchio stile funziona lo stesso.

+0

L'utilizzo di 'functools.partial' è un'ottima idea. +1 – delnan

+0

Grazie. Vado con la soluzione functools.partial. – lukad

2

Si stanno creando chiusure. Le chiusure catturano davvero una variabile, non il valore di una variabile. Alla fine di __init__, i è l'ultimo elemento di range(0, 10), ad esempio 9. Tutti i lambda creati in questo ambito si riferiscono a questo i e solo quando vengono richiamati ottengono il valore di i nel momento in cui sono invocati (tuttavia, invocazioni separate di __init__ creano lambda che si riferiscono a variabili separate!).

Ci sono due modi popolari per evitare questo:

  1. utilizzando un parametro predefinito: lambda i=i: self._number(i). Questo funziona perché i parametri predefiniti associano un valore al momento della definizione della funzione.
  2. Definizione di una funzione di supporto helper = lambda i: (lambda: self._number(i)) e uso helper(i) nel ciclo. Questo funziona perché il "esterno" i viene valutato al momento i associato e, come accennato in precedenza, la chiusura successiva creata nella prossima chiamata di helper farà riferimento a una variabile diversa.
0

Utilizzare il modo Qt, utilizzare invece QSignalMapper.

+1

Per favore non farlo. 'QSignalMapper' è un residuo di C++ 98, che non ha funzioni lambda o parziali e dove tale soluzione è un male necessario.In Python tuttavia 'QSignalMapper' è semplicemente superfluo e veramente gonfio rispetto a' functools.partial() 'o lambda. Le soluzioni con 'partial()' o 'lambda' sono più corte e più eleganti di' QSignalMapper'. – lunaryorn

+0

Per quanto riguarda la scelta, scegliere 'QSignalMapper' per la compatibilità Qt/C++. – ismail

+1

Ovviamente si tratta della scelta, ma semplicemente non capisco la tua scelta e non posso seguirla. Sto usando Python per le applicazioni Qt esattamente perché non è * C++, e se abbandonerei tutte le funzionalità speciali di Python per mantenere o per raggiungere la compatibilità con C++, potrei anche usare C++;) – lunaryorn