2 | ||
Editor: geon
Time: 2011/10/12 20:35:46 GMT+2 |
||
Note: pridavani znacek 2x-3x |
changed: - .. image:: py30.png :align: right Command, Lambda, functools ... Voláme funkce z tlačítka ---------------------------------------- Ukázkový příklad .................. Nejjednoduší příklady nastávají na tutoriálních příkladech tisku, které v praxi téměř nikdy nenastávají ;-) :: # -*- coding: utf-8 -*- from tkinter import * def tisk(): print (1) hlavni=Tk() # všimněte si, že tu není "tisk()"! ale jen "tisk"! tl=Button(text="tiskni", command=tisk) tl.pack() hlavni.mainloop() Za povšimnutí rozhodně stojí, že se nepoužívá *tisk()*, ale jen *tisk*. Použijete-li tisk(), program nebude fungovat. Vyzkoušejte. Funkce s parametrem .................... Častěji chceme funkci volat s nějakým parametrem Z předchozího odstavce již víme, že prosté *command=tisk(10)* fungovat nebude. Můžeme použít lambda funkci:: # -*- coding: utf-8 -*- from tkinter import * def tisk(x): print (x) hlavni=Tk() # všimněte si, že tu není jen "tisk()"! ale lambda: tisk() tl=Button(text="tiskni", command= lambda: tisk(10)) tl.pack() hlavni.mainloop() Více tlačítek na jednu funkci s parametrem ........................................... Nefunkční kód může vypadat třeba takto:: 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! Řešení s lambda: ,,,,,,,,,,,,, Problem 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 pokud je definovana na globalni urovni:: lambda: pis(LOAD_GLOBAL('prvek')) 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. Ale necistsi reseni bez triku (a jeste pomerne kratke) je asi tohle:: def gen_pis_prvek(prvek): return lambda: pis(prvek) for prvek in cisla: menu.add_cascade(label=prvek, command=gen_pis_prvek(prvek)) Řešení s modulem functools: ,,,,,,,,,,, Od Python 2.5 lze taky využít nový standardní modul functools a jím definovanou funkci *partial()* -- viz dokumentace *"6.6 functools -- Higher order functions and operations on callable objects. "* Příklad pak lze přepsat takto:: import functools 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=functools.partial(pis, prvek)) menubar.add_cascade(label="cisla",menu=menu) okno.config(menu=menubar) mainloop()
Nejjednoduší příklady nastávají na tutoriálních příkladech tisku, které v praxi téměř nikdy nenastávají ;-)
# -*- coding: utf-8 -*- from tkinter import * def tisk(): print (1) hlavni=Tk() # všimněte si, že tu není "tisk()"! ale jen "tisk"! tl=Button(text="tiskni", command=tisk) tl.pack() hlavni.mainloop()
Za povšimnutí rozhodně stojí, že se nepoužívá tisk(), ale jen tisk. Použijete-li tisk(), program nebude fungovat. Vyzkoušejte.
Častěji chceme funkci volat s nějakým parametrem Z předchozího odstavce již víme, že prosté command=tisk(10) fungovat nebude. Můžeme použít lambda funkci:
# -*- coding: utf-8 -*- from tkinter import * def tisk(x): print (x) hlavni=Tk() # všimněte si, že tu není jen "tisk()"! ale lambda: tisk() tl=Button(text="tiskni", command= lambda: tisk(10)) tl.pack() hlavni.mainloop()
Nefunkční kód může vypadat třeba takto:
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!
Problem 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 pokud je definovana na globalni urovni:
lambda: pis(LOAD_GLOBAL('prvek'))
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.
Ale necistsi reseni bez triku (a jeste pomerne kratke) je asi tohle:
def gen_pis_prvek(prvek): return lambda: pis(prvek) for prvek in cisla: menu.add_cascade(label=prvek, command=gen_pis_prvek(prvek))
Od Python 2.5 lze taky využít nový standardní modul functools a jím definovanou funkci partial() -- viz dokumentace "6.6 functools -- Higher order functions and operations on callable objects. "
Příklad pak lze přepsat takto:
import functools 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=functools.partial(pis, prvek)) menubar.add_cascade(label="cisla",menu=menu) okno.config(menu=menubar) mainloop()