Seite 1 von 1

PyGTK callback, Argument für Funktion übergeben

Verfasst: Freitag 22. Januar 2010, 10:37
von virginiawalker
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.

Re: PyGTK callback, Argument für Funktion übergeben

Verfasst: Freitag 22. Januar 2010, 10:41
von /me
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?

Re: PyGTK callback, Argument für Funktion übergeben

Verfasst: Freitag 22. Januar 2010, 10:45
von virginiawalker
/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 ;-)

Verfasst: Freitag 22. Januar 2010, 10:48
von Michael Schneider
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

Verfasst: Freitag 22. Januar 2010, 12:35
von ms4py
@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+ ;)

Verfasst: Freitag 22. Januar 2010, 13:23
von virginiawalker
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

Verfasst: Freitag 22. Januar 2010, 13:31
von ms4py
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).

Verfasst: Freitag 22. Januar 2010, 20:54
von Michael Schneider
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

Verfasst: Freitag 22. Januar 2010, 22:07
von EyDu
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.

Verfasst: Samstag 23. Januar 2010, 09:38
von Michael Schneider
Stimmt, ist ein wenig eleganter als der workaround mit lambda. Gibt es aber auch erst ab Python 2.5.