Python: Implementing a series of functions with each one calling the next
Question: programming isn't my field, but I'm trying to learn. I've been writing a
program that works something like this:

    
    
    from Tkinter import *
    root=Tk()
    
    def Secondwindow():
        firstframe.destroy()
        secondframe = Frame(root)
        secondframe.pack()
        secondcontent = Label(secondframe, text = 'second window content').pack()
        def Thirdwindow():
            secondframe.destroy()
            thirdframe = Frame(root)
            thirdframe.pack()
            thirdcontent = Label(thirdframe, text = 'third window content').pack()
            def Fourthwindow():
                thirdframe.destroy()
                fourthframe = Frame(root)
                fourthframe.pack()
                fourthcontent = Label(fourthframe, text = 'fourth window content').pack()
            thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
        secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
    firstframe = Frame(root)
    firstframe.pack()
    firstcontent = Label(firstframe, text = 'first window content').pack()
    firstbutton = Button(firstframe, text = 'Next ->', command = Secondwindow).pack()
    
    root.mainloop()
    

Now, this works perfectly, but as my program gets larger and more complicated
I am starting to see that this is neither elegant nor easy to maintain. I
would like to simply write each function in (more or less) sequence, but that
causes namerrors when the program reads a reference to a function that hasn't
been defined yet (it seems like the program shouldn't worry about it until it
has to run the function, by which time it would have already seen the function
definition, but oh well).

What is the simplest way to have this functionality (functions called from
within functions) without having to stick the next function definition in the
middle of the first function definition? Thanks in advance!


Answer: I un-nested the functions to see what the error was. The problem you have is
that the functions try to access variables defined in the scope of another
function. That won't work. You either have to nest functions so that their
scopes overlap, as you did -- which is awkward -- or you have to use global
variables -- which is less awkward, but still awkward -- or you have to pass
variable names from function to function.

However, because you're using callbacks here -- which are quite advanced! --
executing the third option is more complicated. If you really want to get this
working, I would suggest an object-oriented approach. But frankly I would
suggest starting with something simpler than this for a beginning programmer.

The most important thing is that you get used to scoping rules. That, at
least, I can explain with your code. Here's an explanation of the NameErrors
you were getting.

    
    
    def Secondwindow():
        firstframe.destroy()
        secondframe = Frame(root)
        secondframe.pack()
        secondcontent = Label(secondframe, text = 'second window content').pack()
        secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
    def Thirdwindow():
        secondframe.destroy()
        thirdframe = Frame(root)
        thirdframe.pack()
        thirdcontent = Label(thirdframe, text = 'third window content').pack()
        thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
    

These two functions look like they do almost the same thing. But they don't!
Here's why:

    
    
    def Secondwindow():
        firstframe.destroy()
    

This line refers to `firstframe`, which was defined in the global scope (i.e.
at the 'lowest level' of the program. That means it can be accessed from
anywhere. So you're ok here.

    
    
        secondframe = Frame(root)
        secondframe.pack()
        secondcontent = Label(secondframe, text = 'second window content').pack()
        secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
    

These variables are all defined within the scope of `Secondwindow`. That means
they _only exist_ within `Secondwindow`. Once you leave `Secondwindow`, they
cease to exist. There are good reasons for this!

    
    
    def Thirdwindow():
        secondframe.destroy()
    

Now you run into your problem. This tries to access `secondframe`, but
`secondframe` is only defined within `Secondwindow`. So you get a `NameError`.

    
    
        thirdframe = Frame(root)
        thirdframe.pack()
        thirdcontent = Label(thirdframe, text = 'third window content').pack()
        thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
    

Again, these are all defined only within the scope of `ThirdWindow`.

Now, I can't explain everything you need to know to make this work, but here's
a basic hint. You can create a global variable within a function's namespace
by saying

    
    
    global secondframe
    secondframe = Frame(root)
    

Normally python assumes that variables defined in a function are local
variables, so you have to tell it otherwise. That's what `global secondframe`
does. Now you really shouldn't do this very often, because as the global scope
fills up with more and more variables, it becomes harder and harder to work
with them. Functions create smaller scopes (or 'namespaces' as they're called
in some contexts) so that you don't have to keep track of all the names (to
make sure you don't use the same name in two places, or make other even more
disastrous mistakes).

Normally, to avoid creating a global variable, you would have each function
return the frame it defines by calling `return secondframe`. Then you could
add a function argument to each function containing the previous frame, as in
`def Thirdwindow(secondframe)`. But because you're using callbacks to call
`Secondwindow`, etc., this method gets knotty. Here's some code that works
around the problem by using `lambda` statements.

    
    
    from Tkinter import *
    root=Tk()
    
    def Secondwindow(firstframe):
        firstframe.destroy()
        secondframe = Frame(root)
        secondframe.pack()
        secondcontent = Label(secondframe, text = 'second window content').pack()
        secondbutton = Button(secondframe, text = 'Next ->', command = lambda: Thirdwindow(secondframe)).pack()
    def Thirdwindow(secondframe):
        secondframe.destroy()
        thirdframe = Frame(root)
        thirdframe.pack()
        thirdcontent = Label(thirdframe, text = 'third window content').pack()
        thirdbutton = Button(thirdframe, text = 'Next ->', command = lambda: Fourthwindow(thirdframe)).pack()
    def Fourthwindow(thirdframe):
        thirdframe.destroy()
        fourthframe = Frame(root)
        fourthframe.pack()
        fourthcontent = Label(fourthframe, text = 'fourth window content').pack()
    
    firstframe = Frame(root)
    firstframe.pack()
    firstcontent = Label(firstframe, text = 'first window content').pack()
    firstbutton = Button(firstframe, text = 'Next ->', command = lambda: Secondwindow(firstframe)).pack()
    
    root.mainloop()
    

But the best way to fix this is to use object-oriented code. Unfortunately
that's just too complex a topic to get into; it would just add more verbiage
to an already long post. I honestly think you should spend some time getting
used to functions and scoping first.

* * *

That said, I found a moment to fiddle with an object-oriented variation. Here
it is:

    
    
    from Tkinter import *
    root=Tk()
    
    class FrameRepeater(object):
        def __init__(self, start=0, end=4):
            self.frame = None
            self.number = start
            self.end = end
    
        def new_frame(self):
            if self.frame:
                self.frame.destroy()
            self.frame = Frame(root)
            self.frame.pack()
            self.content = Label(self.frame, text = 'window ' + str(self.number) + ' content')
            self.content.pack()
            self.button = Button(self.frame, text = 'Next ->', command = self.replace)
            self.button.pack()
            self.number += 1
    
        def replace(self):
            if self.number < self.end:
                self.new_frame()
            elif self.number >= self.end:
                self.content.config(text='Press button again to quit')
                self.button.config(command=self.quit)
    
        def quit(self):
            self.frame.destroy()
            root.destroy()
            exit()
    
    FrameRepeater().new_frame()
    root.mainloop()
    

A couple of things to note. First, in those lines that read like this, there's
a subtle error:

    
    
    thirdcontent = Label(thirdframe, text = 'third window content').pack()
    

You were storing `None` in `thirdcontent`, because the `pack()` method has no
return value. If you want to preserve a reference to the `Label`, you have to
save the reference first, then `pack()` it separately, as I did in `new_frame`
above.

Second, as you can see from my `replace` method, you don't actually have to
destroy the frame to change the text of the label _or_ the button command! The
above still destroys the first three frames just to show how it would work.

Hope this gets you started! Good luck.

