[python] (no subject)

Radek Kanovsky rk na dat.cz
Sobota Říjen 28 13:33:41 CEST 2006


On Sat, Oct 28, 2006 at 10:10:39AM +0200, Jakub Vojáček wrote:

> mám problém tady stím:
> 
> from Tkinter import*
> def pis(co):
>     print co
> okno=Tk()
> menubar = Menu(okno)
> menu = Menu(menubar, tearoff=0)
> cisla=[1,2,3,4,5,6,7,8,9,10]
> for prvek in cisla:      
>     menu.add_cascade(label=prvek,command=lambda: pis(prvek))
> menubar.add_cascade(label="cisla",menu=menu)    
> okno.config(menu=menubar)
> mainloop()
> 
> když v tom menu kliknu na jakoukoliv položku tak se vždy napíše
> 10? proč?mělo by to přece napsat to číslo na který klikám

Prolem je v definici lambda funkce. Promenna 'prvek' je v te funkci jako
volna promenna a zkompilovana lambda vypada nejak takto:

    lambda: pis(LOAD_DEREF('prvek'))

    Nebo lambda: pis(LOAD_GLOBAL('prvek')), pokud je definovana na
    globalni urovni.

Ale ne takto:

    lambda: pis(1)
    lambda: pis(2)
    ....

Cili ve vasem pripade v tom cyklu v podstate vznikne 10 shodnych
funkci, ktere si hodnotu prvku zjistuji az za behu a ta je
po skonceni cyklu rovna hodnote 10. Vice o tom najdete pres
klicova slova "python closures".

Aby to fungovalo, musi se pouzit nejaky trik:

    lambda p=prvek: pis(p)

nebo

    new.instancemethod(lambda p:pis(p), prvek, type(prvek))

Tim se vygenerovana instance lambda funkce vzdy svaze s konkretnim prvkem.
Prvni varianta je jednodussi, ale meni signaturu funkce, coz muze nekdy
vadit. Druha verze vytvori fiktivni anonymni metodu konkretniho objektu
prvek (bound method), ikdyz trida int zadnou takovou metodu nema.
Je to vlastne zkracena verze varianty s pomocnou tridou:

    class PisInt:
        def __init__(self, prvek):
            self.p = prvek
        def __call__(self):
            return pis(self.p)

    for prvek in cisla:      
        menu.add_cascade(label=prvek,command=PisInt(prvek))


Radek Kaňovský


Další informace o konferenci Python