partial mit Methoden-Parameter

Fragen zu Tkinter.
Antworten
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Bekannterweise kann ich wie in

Code: Alles auswählen

from functools import partial
def foo(x,y): print('jo:',x,y)
fuu = partial(foo,y=2)
fuu(5)
eine neue Funktion mit festen Argumentwerten definieren.
Wie mache ich das aber, wenn foo nun eine Methode sein soll, also bei

Code: Alles auswählen

class Kl(object):
   def boo(self,x,y): print('jo:',x,y)
   buu = partial(boo,y=2)  #FALSCH, da kein self gegeben!
Wo bringe ich das self unter :( ? (Ich brauche das natürlich immer wieder wegen der tkinter-Aufruffunktionen)
Zuletzt geändert von Anonymous am Mittwoch 1. August 2012, 17:45, insgesamt 2-mal geändert.
Grund: Syntaxhervorhebung aktiviert
deets

Du musst natuerlich irgendwo "partial(self.boo, y=2)" machen, wenn du den Callback setzt. Oder "partial(inztanz_von_kl.boo, y=2)".
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@deets: natürlich *irgend*wo. Aber meine Frage war ja *wo?*!

Ich mach einen zweiten Anlauf:

Code: Alles auswählen

class Kl(object):
   def boo(self,x=None,y=None): print('jo:',x,y)
   def __init__(self):
      self.fix1x = partial(self.boo, 0)  #ok
      self.fix2y = partial(self.boo, y=0)  #ok
      self.fix2x = partial(self.boo, x=0)  #FALSCH, aber warum?
#
kl = Kl()
kl.boo(2,3)
kl.fix1x(5)  #ok
kl.fix2y(5)  #ok
kl.fix2x(5)  #got multiple values for x!!
Wenn das nicht wirr ist! Warum ist das eine richtig und das andere falsch??
Zuletzt geändert von Hyperion am Mittwoch 1. August 2012, 18:51, insgesamt 1-mal geändert.
Grund: Code in Python-Code Tags gesetzt.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Naja, `x` ist Dein *erster* Parameter... und den bindest Du als `keyword` Parameter" in `partial`. Wie wir wissen, darf man keine `non-keyword` Parameter nach dem ersten übergebenen `keyword` Parameter keine `non-keyword` Parameter mehr an eine Funktion übergeben:

Code: Alles auswählen

kl.boo(1, 2)
>>> jo: 1 2

kl.boo(x=1, 2)
>>>  File "<ipython-input-47-07111cd32d7b>", line 1
SyntaxError: non-keyword arg after keyword arg
`partial` wird da so schlau sein und dieses Verhalten abbilden.

So klappt's:

Code: Alles auswählen

kl.fix2x(y=5)
>>> jo: 0 5
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Ok, das wäre doch schon mal etwas. Aber wenn ich das jetzt in tkinter einsetze, funktioniert es wieder nicht:

Code: Alles auswählen

import tkinter as t
from functools import partial

class Fenster(object):
   def tuwas(self,x=None,y=None): print('jo:',x,y)

   def __init__(self):
      knopf = t.Button(width=20,text="drücken -> fehler")
      knopf.grid()
      #
      #tuwas_festes = partial(self.tuwas,6,7) #  #wrong number of arguments
      tuwas_festes = partial(self.tuwas,x=6,y=7) #multiple values for x
      knopf.bind('<ButtonRelease-1>',tuwas_festes)
      #
      t.mainloop()

Fenster()
Zuletzt geändert von Hyperion am Mittwoch 1. August 2012, 19:36, insgesamt 1-mal geändert.
Grund: Code in Python-Code Tags gesetzt. Bitte achte da doch mal selber drauf!
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Das kann eigentlich nicht sein:

Code: Alles auswählen

f = partial(kl.boo, 1, 2)
f()
>>> 1 2

f = partial(kl.boo, x=1, y=2)
f()
>>> 1 2
Alles noch aus Deinem ersten Beispiel.

Bist Du sicher, dass der Fehler nicht erst nach dem `bind` kommt? Evtl. wird da ein Parameter an das Partial-Objekt weitergegeben... und dann kracht's natürlich.

Edit: Ich hab es grad mal ausporbiert... es ist genau so, wie ich es vermutet habe! Das Anlegen klappt prima, krachen tut es nach dem `bind`.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Hyperion hat geschrieben:Bist Du sicher, dass der Fehler nicht erst nach dem `bind` kommt?
Ich habe den Code, so wie du ihn im tkinter-Beispiel siehst, herausgelöst und genau *so* laufen lassen, da sind keine zusätzlichen Anweisungen vorhanden. Was tkinter an das partial-Objekt *intern* alles weitergibt, weiß ich natürlich nicht...

NACHTRAG:
Es liegt am 'bind'; folgendes funktioniert nämlich:

Code: Alles auswählen

import tkinter as t
from functools import partial

class Fenster(object):
   def tuwas(self,x=None,y=None): print('jo:',x,y)

   def __init__(self):
      knopf = t.Button(width=20,text="drücken -> fehler")
      knopf.grid()
      #
      tuwas_festes = partial(self.tuwas,x=6,y=7)
      #knopf.bind('<ButtonRelease-1>',tuwas_festes)  #FALSCH
      knopf.config(command=tuwas_festes)  #ok
      #
      t.mainloop()

Fenster()
Frägt sich jetzt nur noch, was hier am 'bind' falsch ist; oft braucht man ja das 'bind'.
Zuletzt geändert von Goswin am Mittwoch 1. August 2012, 20:09, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Goswin hat geschrieben:
Hyperion hat geschrieben:Bist Du sicher, dass der Fehler nicht erst nach dem `bind` kommt?
Was tkinter an das partial-Objekt *intern* alles weitergibt, weiß ich natürlich nicht...
Ja und genau deswegen kracht es! Ich habe das ja auch mal ausprobiert:

Code: Alles auswählen

In [60]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:import tkinter as t
:from functools import partial
:
:class Fenster(object):
:   def tuwas(self,x=None,y=None): print('jo:',x,y)
:
:   def __init__(self):
:      knopf = t.Button(width=20,text="drücken -> fehler")
:      knopf.grid()
:      #
:      #tuwas_festes = partial(self.tuwas,6,7) #  #wrong number of arguments
:      tuwas_festes = partial(self.tuwas,x=6,y=7) #multiple values for x
:      knopf.bind('<ButtonRelease-1>',tuwas_festes)
:      #
:      t.mainloop()
:
:Fenster()
:
:--

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python3.2/tkinter/__init__.py", line 1399, in __call__
    return self.func(*args)
TypeError: tuwas() got multiple values for keyword argument 'x'
Out[60]: <__main__.Fenster at 0x228b090>
Die Ausnahme kommt erst *nach* dem Druck auf den Knopf. Also bekommt Dein `partial`-Objekt noch Parameter übergeben...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
deets

Goswin hat geschrieben:Frägt sich jetzt nur noch, was hier am 'bind' falsch ist; oft braucht man ja das 'bind'.
Wenn du nur halb so gut im Doku lesen waerest wie im beschweren, dann haettest du das hier gelesen:

"""
If an event matching the event description occurs in the widget, the given handler is called with an object describing the event.
"""

http://www.pythonware.com/library/tkint ... ndings.htm

Ups. Da wird wohl noch ein Event-Objekt uebergeben, wenn man bind benutzt.
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

@Goswin
Ich benutze lieber 'lambda'. Da sieht man besser, wie und wo die Argumente verwendet werden:

Code: Alles auswählen

import Tkinter as tk


class Gui(object):

    def __init__(self):
        self.root = tk.Tk()
        self.root.bind('<Button-1>', lambda event, z=42: self.doit(event, 1, 2, 3, z, z=0, x=4, y=5))


    def doit(self, event, a, *args, **kwargs):
        print event, a, args, kwargs


    def run(self):
        self.root.mainloop()


if __name__ == '__main__':
    Gui().run()
lunar

@yipyip: Auf der anderen Seite erzeugt "lambda" Closures, vgl:

Code: Alles auswählen

>>> i = 10
>>> f = lambda: print(i)
>>> f()
10
>>> i = 20
>>> f()
20
Das führt schnell zu unbeabsichtigten Effekten, wenn man beispielsweise Callbacks in einer Schleife erzeugt. Darüber bin ich selbst schon gestolpert. Deswegen rate ich Anfängern immer zu "partial()".
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

@lunar
Stimmt, da muss man aufpassen. Dieses 'Gotcha' habe ich auch schon leidvoll erfahren muessen. :)
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

deets hat geschrieben: Wenn du nur halb so gut im Doku lesen waerest wie im beschweren, dann haettest du das hier gelesen:
"If an event matching the event description occurs in the widget, the given handler is called with an object describing the event."
Richtig, das ist die Info, die ich brauchte, und jetzt funktioniert es auch.

Geschätzter deets, du weißt gar nicht, wie beliebt du wärest, wenn du bei deiner Hilfestellung uns vergessliche Handbuchleser nicht gleich ausschimpfen würdest :wink: !
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

lunar hat geschrieben:Auf der anderen Seite erzeugt "lambda" Closures, vgl:

Code: Alles auswählen

>>> i = 10
>>> f = lambda: print(i)
>>> f()
10
>>> i = 20
>>> f()
20
Ich habe den Eindruck, dieses "Closure-Verhalten" ist genau das, was ich brauche: ich verwende f in einer Schleife über i und wünsche mir, dass es den aktuellen Wert von i ausgibt. Also ziemlich cool das Ganze!

(Um so klar wie möglich zu sein werde ich vielleicht die lambda-Definition in die Schleife hineinschieben, obwohl das voraussichtlicht langsamer ist).
lunar

@Goswin: Bist Du Dir sicher, dass Du wirklich dieses Verhalten wünschst? Anders gefragt: Weißt Du, welcher Wert in folgendem Quelltext ausgegeben wird?

Code: Alles auswählen

functions = []
for i in xrange(10):
    functions.append(lambda: print(i))
functions[1]()
deets

Goswin hat geschrieben: Geschätzter deets, du weißt gar nicht, wie beliebt du wärest, wenn du bei deiner Hilfestellung uns vergessliche Handbuchleser nicht gleich ausschimpfen würdest :wink: !
Das unterstellt, das mir Beliebtheit wichtiger waere als Spass an provokanten Formulierungen zu haben! Und ich versuche meine schandmaeuligkeit mit dem Willen des Posters zu korrelieren, den Fehler erstmal bei sich statt bei Python oder dem Rest der Welt zu suchen ;)
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

lunar hat geschrieben:@Goswin: Bist Du Dir sicher, dass Du wirklich dieses Verhalten wünschst?
Du hast recht, lambda lässt sich für callbacks nicht verwenden: bei Python nimmt das Argument der Funktion den Wert an, den es beim *Aufruf* des Callbacks hat, und nicht den Wert bei der *Definition* des Callbacks. :( Und das ist *nicht*, was ich brauche.
lunar

@Goswin: "i" ist kein Argument, sondern ein sogenannter „freier Name“. Python bindet freie Namen nicht über ihren Wert, sondern über ihren Namen. "i" innerhalb des "lambda"s ist also keine Kopie von "i", sondern eine Referenz darauf. "i" verhält sich zum "lambda" wie eine globale Variable zu einer normalen Funktion.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@lunar: Richtig.
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

@Goswin
Ob man 'lambda' oder 'partial' verwendet, haengt im allgemeinen nicht vom Problem ab, man muss die Sachen nur richtig anwenden.
Um das Ganze mal aufzudröseln, ein einfaches Beispiel mit lambda:

Code: Alles auswählen

import Tkinter as tk


class Gui(object):

    def __init__(self):
        self.root = tk.Tk()
        self.buttons = [tk.Button(self.root, width=20, text='{0}'.format(i),
                                  command=lambda j=i: self.do_command(j)) for i in xrange(9)]
        for but in self.buttons:
            but.pack()
        for i, but in enumerate(self.buttons):
            but.bind('<Button-1>', lambda event, j=i: self.do_binding(event, j))


    def do_command(self, i):
        print 'command {0}'.format(i)


    def do_binding(self, event, i):
        print 'binding {0} {1}'.format(i, event.widget)


    def run(self):
        self.root.mainloop()


if __name__ == '__main__':
    Gui().run()
Und hier mit partial:

Code: Alles auswählen

import Tkinter as tk
from functools import partial


class Gui(object):

    def __init__(self):
        self.root = tk.Tk()
        self.buttons = [tk.Button(self.root, width=20, text='{0}'.format(i),
                                  command=partial(self.do_command, i)) for i in xrange(9)]
        for but in self.buttons:
            but.pack()
        for i, but in enumerate(self.buttons):
            but.bind('<Button-1>', partial(self.do_binding, i))


    def do_command(self, i):
        print 'command {0}'.format(i)


    def do_binding(self, i, event):
        print 'binding {0} {1}'.format(i, event.widget)


    def run(self):
        self.root.mainloop()


if __name__ == '__main__':
    Gui().run()
Antworten