PyGTK callback, Argument für Funktion übergeben

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
virginiawalker
User
Beiträge: 6
Registriert: Donnerstag 21. Januar 2010, 09:12
Wohnort: Ulm

Hallo,

Da ich keine Sektion für PyGTK gefunden hab, poste ich das Problem mal hier rein.

Das Problem: Wenn in einen Knopf mit einer Funktion verbinde (connect),
lassen sich keine Argumente übergeben ohne Fehler zu verursachen.

Code: Alles auswählen

#!/usr/bin/env python

import gtk,pygtk
pygtk.require('2.0')
import os,sys,commands,subprocess

def crypt_mount(mount):
    OPEN_CMD='cryptsetup luksOpen ';TARGET=' LV04'
    CMD_ALL=OPEN_CMD + mount + TARGET
    print(CMD_ALL)
    cmd_process = subprocess.Popen(OPEN_CMD + mount + TARGET, stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    result = cmd_process.communicate()[0]
    print result


DEVICE='/dev/mapper/VG00-LV04'
window = gtk.Window()
window.set_size_request(300,-1)
window.set_title("Crypt devices")
window.set_resizable(True)
window.set_position(gtk.WIN_POS_CENTER)
vbox = gtk.VBox(False, 0)
vbox.show()
window.add(vbox)
button = gtk.Button(DEVICE)
button.connect("clicked", crypt_mount(DEVICE))
vbox.pack_start(button)
button.show()
Output:
-----------
cryptsetup luksOpen /dev/mapper/VG00-LV04 LV04

Traceback (most recent call last):
File "./args.py", line 26, in <module>
button.connect("clicked", crypt_mount(DEVICE))
TypeError: second argument must be callable
-----------

Wenn ich der Funktion "crypt_mount" nichts übergebe, kommt kein Fehler.
Folgende Weise funktioniert auch nicht.

Code: Alles auswählen

button.connect("clicked", crypt_mount(), DEVICE)
Hier kommt der Fehler, "crypt_mount" würde genau 1 Argument benötigen, habe aber
0 angegeben.

Bisher hab ich es vermieden, im Callback (connect) Argumente zu übergeben - kennt jemand einen Weg wie das geht. Ich bin nach 2h googlen am Ende mit dem Latein.

Danke.
Never miss a good opportunity to shut up ;-)
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

virginiawalker hat geschrieben:Da ich keine Sektion für PyGTK gefunden hab, poste ich das Problem mal hier rein.
Was gefällt dir an GTK+/Gnome nicht?
virginiawalker
User
Beiträge: 6
Registriert: Donnerstag 21. Januar 2010, 09:12
Wohnort: Ulm

/me hat geschrieben:
virginiawalker hat geschrieben:Da ich keine Sektion für PyGTK gefunden hab, poste ich das Problem mal hier rein.
Was gefällt dir an GTK+/Gnome nicht?
Nichts. Ich hab einfach nur mit PyGTK angefangen. Was nicht ist kann ja noch werden ;-)
Never miss a good opportunity to shut up ;-)
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Moin,

sieht nach dem üblichen Problem aus:
Wenn Du eine Funktion als Parameter übergeben möchtest, darfst Du nur ihren Namen angeben. Sobald Du hinter der zu übergebenden Funktion eine Klammer öffnest, wird sie sofort ausgeführt.

[edit: überarbeitet]Du musst eine Funktion definieren, die eine Funktion erzeugt, welche Deine Funktion mit dem gewünschten Argument aufruft:

Code: Alles auswählen

func_gen = lambda func, *args, **kw: lambda: func(*args, **kw)
#...
button.connect("clicked", func_gen(crypt_mount, DEVICE))
[/edit]

Grüße,
Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

@Michael, doch nicht so umständlich o_O

Das geht so:

Code: Alles auswählen

def func(button, param):
    pass

button.connect('clicked', func, param)
P.S. In das oben erwähnte Forum gehören selbstverständlich die PyGTK-Fragen rein, PyGTK ist nur ein Wrapper für GTK+ ;)
virginiawalker
User
Beiträge: 6
Registriert: Donnerstag 21. Januar 2010, 09:12
Wohnort: Ulm

Michael Schneider hat geschrieben:Moin,

sieht nach dem üblichen Problem aus:
Wenn Du eine Funktion als Parameter übergeben möchtest, darfst Du nur ihren Namen angeben. Sobald Du hinter der zu übergebenden Funktion eine Klammer öffnest, wird sie sofort ausgeführt.

[edit: überarbeitet]Du musst eine Funktion definieren, die eine Funktion erzeugt, welche Deine Funktion mit dem gewünschten Argument aufruft:

Code: Alles auswählen

func_gen = lambda func, *args, **kw: lambda: func(*args, **kw)
#...
button.connect("clicked", func_gen(crypt_mount, DEVICE))
[/edit]
Hmm... vom Prinzip her versteh ichs.
Einfache Funktionen mit Lambda kann ich nachvollziehen:
z.B.

Code: Alles auswählen

f=lambda var1,var2: var1 + var2
Soweit ists klar.
Auf den callback hier müsste das ja dann wie folgt aussehen, oder ?

Code: Alles auswählen

caller=lambda CM: CM(DEVICE)
func=caller(crypt_mount)
Das müsste doch dann "crypt_mount(DEVICE)" ergeben.

Damit hab ichs getestet,

Code: Alles auswählen

caller=lambda CM: CM(DIFF_MNT)
caller2=caller(self.crypt_mount)
button.connect("clicked", self.caller2)
Geht auch nicht.

Ok. Hier sind die Code-snippets aus dem eigentlichen Programm.
Egal welche Variante, ich bekomme entweder
zu wenig oder zu viele Argumente für die Funktion.
Da kann man glaub mit "self." auch was falsch machen, oder ?

Code: Alles auswählen

class Master:
    def __init__(self):
        # Create the toplevel window
        window = gtk.Window()
....
    def pointer(self, param):
        self.crypt_mount(param)
....
def crypt_mount(self, mount):
        MNT_CMD='cryptsetup luksOpen ';TARGET=' /mnt'
        cmd_process = subprocess.Popen(MNT_CMD + mount + TARGET, stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
        result = cmd_process.communicate()[0]
#        return result
        print result
....
    DIFF_ALL='%s - %s %s' %(DIFF_I3,LVX_line[0],LVX_line[1])
    button = gtk.Button(DIFF_ALL)
#  caller=lambda CM: CM(DIFF_MNT)
#  caller2=caller(self.crypt_mount)
    button.connect("clicked", self.pointer, DIFF_MNT)
    vbox.pack_start(button)
    button.show()
....
Das Programm sucht alle luks Laufwerke raus, passt die Strings an, zieht die
Laufwerke aus crypttab ab und stellt alles Übrige in Buttons dar.
Jetzt will ich jeden erzeugten Button mit "cryptsetup luksOpen DEV NAME" öffnen.
Ich hab wirklich nur noch Probleme zu verstehen, wie genau ich dem callback
im connect ein Argument übergebe. Irgendwas mach ich noch falsch...

Danke schon mal für die bisherigen Tipps.
Gruß,
Gunter
Never miss a good opportunity to shut up ;-)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

In der Referenz siehst du doch, wie die CB-Funktion aussehen muss:
The "clicked" gtk.Button Signal
def callback(button, user_param1, ...)
Bei einer Methode muss das "self" halt noch als erster Parameter dazu.

Vergiss das außerdem mit dem lambda, das brauchst du in dem Fall nicht (wie bereits erwähnt).
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

ice2k3 hat geschrieben:@Michael, doch nicht so umständlich o_O

Das geht so:

Code: Alles auswählen

def func(button, param):
    pass

button.connect('clicked', func, param)
Ich hatte leider keine PyGTK Dokumentation zur Hand.
Aber generell: wenn man weiß, dass man immer dieselbe Variable verwendet, kann man auch

Code: Alles auswählen

func = lambda DEVICE=DEVICE:  crypt_mount(DEVICE)
schreiben. Aber wenn es optionale Agrumente für Callback Funktionsargumente gibt, sollte man das natürlich bevorzugen. :-)

Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Michael Schneider hat geschrieben:Aber generell: wenn man weiß, dass man immer dieselbe Variable verwendet, kann man auch

Code: Alles auswählen

func = lambda DEVICE=DEVICE:  crypt_mount(DEVICE)
schreiben.
Ich fände ein

Code: Alles auswählen

functools.partial(crypt_mount, DEVICE)
ja schöner als das lambda.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Stimmt, ist ein wenig eleganter als der workaround mit lambda. Gibt es aber auch erst ab Python 2.5.
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Antworten