1

Is it feasible to execute multiple statements in a lambda? The app_data dictionary in my controller has a property "listbox" that stores a selected listbox value. A button on SelectPage has a lambda command that changes frames to NextPage.

Can the frame switch and app_data set statements both occur in a double-lambda? If so, what might this look like? Or do these operations have to occur separately (i.e. in a button click event)?

Update #1:

Issue isolated to lambda expression since just putting a constant (i.e. 1) in like .set(1) does not save either.

Update #2: Using a lambda with doStuff(controller) solves the problem somewhat, but the listbox reference throws an error:

value = self.listbox.get(self.listbox.curselection()) AttributeError: 'SelectPage' object has no attribute 'listbox'

button:

   button1 = ttk.Button(self, text='Next Page',
                         command=lambda:self.doStuff(controller))

doStuff():

    def doStuff(self,controller):
        controller.show_frame(NextPage)
        controller.app_data["listbox"].set(1)
        value = self.listbox.get(self.listbox.curselection())
        print(value)
        print("do Stuff successful")

Full Code:

from tkinter import *
from tkinter import ttk



class MyApp(Tk):
    # Controller class
    def __init__(self):
        Tk.__init__(self)

        # App data in controller
        self.app_data = {"listbox":    StringVar(),
                         "entry":       StringVar(),
                         }

        container = ttk.Frame(self)
        container.pack(side="top", fill="both", expand = True)
        self.frames = {}
        for F in (SelectPage, NextPage):
            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky = NSEW)
        self.show_frame(SelectPage)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

    def get_page(self,classname):
        for page in self.frames.values():
            if str(page.__class__.__name__) == classname:
                return page
        return None

class SelectPage(ttk.Frame):

    def __init__(self, parent, controller):
        self.controller = controller
        ttk.Frame.__init__(self, parent)
        ttk.Label(self, text='Select Page').grid(padx=(20,20), pady=(20,20))

        listbox = Listbox(self,exportselection=0)
        listbox.grid()
        for item in [0,1,2,3,4,5]:
            listbox.insert(END, item)
            print (item)
        entry1 = ttk.Entry(self, textvariable=self.controller.app_data["entry"], width=8)
        entry1.grid()
        button1 = ttk.Button(self, text='Next Page',
                          command=lambda:self.doStuff(controller)) # something like this lambda concept
        button1.grid()

    def doStuff(self,controller):
        controller.show_frame(NextPage)
        controller.app_data["listbox"].set(1)
        value = self.listbox.get(self.listbox.curselection())
        print(value)
        print("do Stuff successful")

class NextPage(ttk.Frame):
    def __init__(self, parent, controller):
        self.controller = controller
        ttk.Frame.__init__(self, parent)
        ttk.Label(self, text='Next Page').grid(padx=(20,20), pady=(20,20))
        button1 = ttk.Button(self, text='Select Page',
                             command=lambda: controller.show_frame(SelectPage))
        button1.grid()
        button2 = ttk.Button(self, text='press to print', command=self.print_it)
        button2.grid()

    def print_it(self):

        value = self.controller.app_data["listbox"].get()
        print ('The value stored in StartPage some_entry = ' + str(value))
        value = self.controller.app_data["entry"].get()
        print ('The value stored in StartPage some_entry = ' + str(value))
app = MyApp()
app.title('Multi-Page Test App')
app.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
Spencer H
  • 653
  • 3
  • 12
  • 30
  • 4
    Possible duplicate of [Is it possible to have multiple statements in a python lambda expression?](http://stackoverflow.com/questions/862412/is-it-possible-to-have-multiple-statements-in-a-python-lambda-expression) – Peter Wood Feb 08 '17 at 18:33
  • 3
    Don't use a lambda, use a proper function with `def`. Note: You can define functions within functions and make use of their local namespace, if necessary. – cadolphs Feb 08 '17 at 18:35
  • What happened when you tried? Does your code work as expected? – Stop harming Monica Feb 08 '17 at 18:36
  • @Goyo Code runs, but listbox item does not get saved – Spencer H Feb 08 '17 at 18:37
  • @Lagerbaer so something like `def doStuff()` with the lambda statements inside (i.e. frame swap and listbox item save), called from lambda like `ttk.Button(self,text='Next Page',command=lambda: doStuff()` – Spencer H Feb 08 '17 at 18:37
  • @Lagerbaer That works – Spencer H Feb 08 '17 at 18:46
  • 2
    Even easier: `def doStuff()` with the statements inside, and then `command=doStuff`. Note: `doStuff`, NOT `doStuff()`. – cadolphs Feb 08 '17 at 18:53
  • @Lagerbaer I pass `self` and `controller` to `doStuff`, so I'll have to include the arguments and stick with the lambda I think. – Spencer H Feb 08 '17 at 18:55
  • Now it works to set listbox in `app_data` =1. But the value from the listboxes cannot be determined because `value = self.listbox.get(self.listbox.curselection()) AttributeError: 'SelectPage' object has no attribute 'listbox'` Here is the code: `def doStuff(self,controller): controller.show_frame(NextPage) controller.app_data["listbox"].set(1) value = self.listbox.get(self.listbox.curselection()) print(value) print("do Stuff")` – Spencer H Feb 08 '17 at 19:11

2 Answers2

2

Your original lambda didn't work because show_frame(...) returns None, which short-circuits the and so the remaining expression doesn't execute. Just change it to an or. Since the event caller doesn't care what you return, you could also create a two item tuple for the two subexpressions. There are other problems, such as self.listbox doesn't exist, which I fixed, but others remain.

As a pedantic aside, lambda only accepts an expression, which is a subset of a statement.

self.listbox = listbox
button1 = ttk.Button(self, text='Next Page',
                  command=lambda: controller.show_frame(NextPage) or self.controller
                     .app_data["listbox"]
                     .set(self.listbox.get(self.listbox.curselection()))) # something like this lam
tdelaney
  • 73,364
  • 6
  • 83
  • 116
1

Is it feasible to execute multiple statements in a lambda?

No. In general, you should avoid using lambda at all, and when you must use it you need to keep it as brief as possible. If you need to call more than a single function, create a new function that calls the other functions.

Also, if you're passing in an argument that is just an attribute of the object, you don't need lambda at all. I think the following code is much easier to read, much easier to understand, much easier to write, and thus much easier to maintain:

button1 = ttk.Button(self, text='Next Page',command=self.doStuff)
...
def doStuff(self):
    self.controller.show_frame(NextPage)
    self.controller.app_data["listbox"].set(1)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685