Seite 1 von 1

partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 17:39
von Goswin
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)

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 17:43
von deets
Du musst natuerlich irgendwo "partial(self.boo, y=2)" machen, wenn du den Callback setzt. Oder "partial(inztanz_von_kl.boo, y=2)".

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 18:34
von Goswin
@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??

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 19:11
von Hyperion
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

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 19:31
von Goswin
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()

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 19:41
von Hyperion
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`.

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 19:53
von Goswin
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'.

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 19:57
von Hyperion
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...

Re: partial mit Methoden-Parameter

Verfasst: Mittwoch 1. August 2012, 22:14
von 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.

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 12:30
von yipyip
@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()

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 13:09
von 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()".

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 13:14
von yipyip
@lunar
Stimmt, da muss man aufpassen. Dieses 'Gotcha' habe ich auch schon leidvoll erfahren muessen. :)

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 15:59
von Goswin
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: !

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 16:29
von Goswin
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).

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 16:38
von 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]()

Re: partial mit Methoden-Parameter

Verfasst: Donnerstag 2. August 2012, 17:43
von 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 ;)

Re: partial mit Methoden-Parameter

Verfasst: Freitag 3. August 2012, 18:33
von Goswin
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.

Re: partial mit Methoden-Parameter

Verfasst: Freitag 3. August 2012, 18:45
von 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.

Re: partial mit Methoden-Parameter

Verfasst: Freitag 3. August 2012, 19:03
von Goswin
@lunar: Richtig.

Re: partial mit Methoden-Parameter

Verfasst: Samstag 4. August 2012, 13:38
von yipyip
@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()