Pergunta

Eu tenho uma lista de strings e deseja criar uma entrada de menu para cada uma dessas cordas. Quando o usuário clica em uma das entradas, sempre a mesma função será chamada com a string como um argumento. Depois de alguma tentativa e pesquisa eu vim com algo como isto:

import sys
from PyQt4 import QtGui, QtCore

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.menubar = self.menuBar()
        menuitems = ["Item 1","Item 2","Item 3"]
        menu = self.menubar.addMenu('&Stuff')
        for item in menuitems:
            entry = menu.addAction(item)
            self.connect(entry,QtCore.SIGNAL('triggered()'), lambda: self.doStuff(item))
            menu.addAction(entry)
        print "init done"

    def doStuff(self, item):
        print item

app = QtGui.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())

Agora o problema é que cada um dos itens do menu irá imprimir a mesma saída: "Item 3" em vez do correspondente. Eu sou grato por quaisquer ideias sobre como posso obter este direito. Obrigado.

Foi útil?

Solução

You're meeting what's been often referred to (maybe not entirely pedantically-correctly;-) as the "scoping problem" in Python -- the binding is late (lexical lookup at call-time) while you'd like it early (at def-time). So where you now have:

    for item in menuitems:
        entry = menu.addAction(item)
        self.connect(entry,QtCore.SIGNAL('triggered()'), lambda: self.doStuff(item))

try instead:

    for item in menuitems:
        entry = menu.addAction(item)
        self.connect(entry,QtCore.SIGNAL('triggered()'), lambda item=item: self.doStuff(item))

This "anticipates" the binding, since default values (as the item one here) get computed once an for all at def-time. Adding one level of function nesting (e.g. a double lambda) works too, but it's a bit of an overkill here!-)

You could alternatively use functools.partial(self.doStuff, item) (with an import functools at the top of course) which is another fine solution, but I think I'd go for the simplest (and most common) "fake default-value for argument" idiom.

Outras dicas

This should work, but I'm pretty sure there was a better way that I can't recall right now.

def do_stuff_caller(self, item):
    return lambda: self.doStuff(item)

...
self.connect(entry, QtCore.SIGNAL('triggered()'), self.do_stuff_caller(item))

Edit: Shorter version, that still isn't what I'm thinking about... or maybe it was in another language? :)

(lambda x: lambda self.do_stuff(x))(item)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top