简体   繁体   中英

Python: Implementing a series of functions with each one calling the next

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!

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 (ie 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.

You can add a parent variable to each function, as that is more or less the only dynamic part of your recursion:

def RecursiveWindow(parent):
    parent.destroy()
    frame = Frame(root)
    frame.pack()
    framContent = Label(frame, text = 'second window content').pack()

    if foo:   # This won't go on forever, will it?
      RecursiveWindow(self)

It looks like you're coding an application with frames and a forward button, like a Windows installer or a slideshow.

Instead of having many frames, each differing only by the text they contain, why not just have one master frame object and the text separate? I don't use Tk for my GUIs, but here's what I mean (might work):

from Tkinter import *

slides = ['Text one', 'Text two', 'Text three', 'cow']
number = 0

root = Tk()
frame = Frame(root).pack()
button = Button(frame, text = 'Next ->', command = NextFrame).pack()


def NextFrame(number):
  frameContent = Label(frame, text = slides[number]).pack()
  number += 1

If you can copy&paste code you can factor it out:

from Tkinter import *
root=Tk()

messages = ['first window content', 'second window content', 'third window content', 'fourth window content' ]

def nextframe(current, messages):
    # what happens when you click the button
    def command():
        current.destroy()
        makeframe(messages)
    return command

def makeframe(messages):        
    frame = Frame(root)
    frame.pack()
    # take the first message
    next_content = Label(frame, text=messages.pop(0)).pack()

    if messages: # if there are more make the button
        next_button = Button(frame, text = 'Next ->', command = nextframe(frame, messages)).pack()

makeframe(messages)
root.mainloop()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM