12 Stimmen

PyQt-Menü aus einer Liste von Strings erstellen

Ich habe eine Liste von Zeichenfolgen und möchte für jede dieser Zeichenfolgen einen Menüeintrag erstellen. Wenn der Benutzer auf einen der Einträge klickt, soll immer die gleiche Funktion mit der Zeichenkette als Argument aufgerufen werden. Nach einigem Ausprobieren und Recherchieren bin ich auf so etwas gekommen:

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_())

Das Problem ist nun, dass jeder der Menüpunkte die gleiche Ausgabe erzeugt: "Punkt 3" anstelle des entsprechenden Punktes. Ich bin für jede Idee dankbar, wie ich das in den Griff bekommen kann. Danke!

25voto

Alex Martelli Punkte 805329

Sie treffen auf das, was oft (vielleicht nicht ganz pedantisch-korrekt;-) als das "Scoping-Problem" in Python bezeichnet wird - die Bindung ist spät (lexikalische Lookup zur Aufrufzeit), während Sie es früh (zur Def-Zeit) möchten. Also, wo Sie jetzt haben:

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

versuchen Sie es stattdessen:

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

Damit wird die Bindung "vorweggenommen", da Standardwerte (wie die item einer hier) werden ein für alle Mal zur Def-Zeit berechnet. Das Hinzufügen einer Ebene der Funktionsverschachtelung (z. B. ein doppeltes Lambda) funktioniert auch, ist hier aber etwas übertrieben!-)

Alternativ können Sie auch functools.partial(self.doStuff, item) (mit einem import functools an der Spitze natürlich), die eine weitere gute Lösung ist, aber ich denke, ich würde für die einfachste (und häufigste) "fake Standardwert für Argument" Idiom gehen.

3voto

Wojciech Bederski Punkte 3815

Das sollte funktionieren, aber ich bin mir ziemlich sicher, dass es einen besseren Weg gab, an den ich mich im Moment nicht erinnern kann.

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

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

bearbeiten : Kürzere Version, das ist immer noch nicht das, woran ich denke... oder vielleicht war es in einer anderen Sprache? :)

(lambda x: lambda self.do_stuff(x))(item)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X