2011-06-01 16 views
2

編程不是我的領域,但我試圖學習。 我一直在寫一個程序,它的工作原理是這樣的:Python:實現一系列功能,每個函數調用下一個

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() 

現在,這個完美的作品,但我的程序變得更大,更復雜,我開始看到這既不優雅,也不便於維護。我想簡單地按照(或多或少)的順序編寫每個函數,但是當程序讀取尚未定義的函數的引用時會導致名稱錯誤(程序似乎不應該擔心,直到它必須運行該功能,到時候它已經看到了功能定義,但是哦)。

有什麼最簡單的方法來實現此功能(函數內調用的函數),而不必在第一個函數定義的中間粘貼下一個函數定義?提前致謝!

+1

你不應該使用「從X導入*」的指令,因爲它拉該模塊的所有符號爲全局命名空間。這可能導致衝突(並且模糊錯誤)。相反,只需'導入Tkinter'並使用完全限定的名稱(如果必須縮短一些,可以'將Tkinter導入爲tk'並參考tk.whatever]。 – user505255 2011-06-01 03:03:31

+0

感謝您的意見,我只是複製了我在其他示例中看到的內容。 – Verdigriss 2011-06-01 03:42:44

回答

2

我未嵌套的功能,看看是什麼錯誤。您遇到的問題是函數嘗試訪問另一個函數範圍中定義的變量。這是行不通的。您必須嵌套函數以便它們的作用域重疊,正如您所做的那樣 - 這很尷尬 - 或者您必須使用全局變量 - 這樣做不那麼笨拙,但仍然很尷尬 - 或者必須從函數傳遞變量名起作用。

但是,因爲您在這裏使用回調 - 這是相當先進的! - 執行第三個選項比較複雜。如果你真的想得到這個工作,我會建議一個面向對象的方法。但坦率地說,我會建議從一開始的程序員開始比這更簡單。

最重要的是您習慣了範圍規則。至少,我可以用你的代碼解釋。以下是你正在獲取的NameErrors的解釋。

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() 

這兩個功能看起來像他們幾乎相同的事情。但他們不!這裏的原因:

def Secondwindow(): 
    firstframe.destroy() 

這條線是指firstframe,這是在全球範圍內(即在程序的「最低水平」定義這意味着它可以從任何地方訪問所以你確定這裏。 。

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

這些變量是所有的Secondwindow範圍內定義,這意味着他們只存在內Secondwindow。一旦你離開Secondwindow,它們就不再存在了。有很好的理由!

def Thirdwindow(): 
    secondframe.destroy() 

現在您遇到了您的問題。這試圖訪問secondframe,但secondframe僅在Secondwindow中定義。所以你得到一個NameError

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

再次,這些都只在ThirdWindow範圍內定義。

現在,我無法解釋所有你需要知道做這項工作,但這裏有一個基本的提示。你可以這樣

global secondframe 
secondframe = Frame(root) 

通常蟒蛇假定在函數中定義的變量是局部變量創建一個函數的命名空間中的全局變量,所以你要明確的告訴它。這就是global secondframe所做的。現在你不應該經常這樣做,因爲隨着全球範圍內變得越來越多,與它們一起工作變得越來越困難。函數創建更小的作用域(或稱爲「命名空間」,因爲它們在某些情況下被調用),以便不必跟蹤所有的名稱(以確保在兩個地方不使用相同的名稱,或使其他更加災難性的錯誤)。

通常,爲了避免創建全局變量,每個函數都會通過調用return secondframe來返回它定義的幀。然後你可以爲包含前一幀的每個函數添加一個函數參數,如def Thirdwindow(secondframe)。但是因爲你使用回調來調用Secondwindow等,所以這個方法很棘手。以下是一些使用lambda語句解決問題的代碼。

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() 

但要解決這個問題的最好辦法就是使用面向對象的代碼。不幸的是,這只是一個太複雜的話題而已;它只會給已經很長的職位增加更多的言辭。老實說,你應該花一些時間習慣功能和範圍。


這就是說,我找到了一個擺脫面向對象變化的時刻。這裏是:

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() 

一些事情要注意。首先,在閱讀這樣的線,有一個細微的錯誤:

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

你被存儲在thirdcontentNone,因爲pack()方法沒有返回值。如果您想保留對Label的引用,則必須先單獨保存該引用,然後再保存pack(),就像我在上面的new_frame中那樣。

其次,從我的replace方法中可以看到,你實際上並不需要銷燬該框架來改變標籤文本的按鈕命令!上面仍然破壞前三個框架,只是爲了說明它如何工作。

希望這會讓你開始!祝你好運。

+0

非常感謝!你知道,當我遇到這個問題時,我確實認爲'啊哈,這是OOP的用處。'但是當我試圖實現它時,我無法實現它。 lambdas的這個修復很簡單,我可以理解它。謝謝你的幫助! – Verdigriss 2011-06-01 04:16:56

+0

@Verdigriss,完全沒問題!順便說一下,請參閱我的底部編輯OO解決方案。 – senderle 2011-06-01 06:12:11

1

您可以將parent變量添加到每個功能,因爲這是或多或少的遞歸的唯一動力部分:

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) 

它看起來像你的編碼與框架和應用一個向前的按鈕,如Windows安裝程序或幻燈片。

爲什麼不只有一個主框架對象和文本是分開的,而不是有許多框架,每個框架只有它們包含的文本不同。我不使用Tk的我的圖形用戶界面,但這裏是我的意思是(可能工作):

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 
+0

非常抱歉,這看起來像一個很好的答案,但我太無知,無法理解這裏發生了什麼。 定義要傳遞給RecursiveWindow的父變量在哪裏? if語句底部發生了什麼? – Verdigriss 2011-06-01 03:52:01

+0

遞歸是當你使用你自己定義的函數時,就像你正在使用窗口一樣。如果你不退出循環,那麼窗口將會繼續生成更多的窗口(當然,如果你想要一個動態數字的話)。 – Blender 2011-06-01 03:55:03

+0

父代變量在您的代碼中定義。它被認爲是「self」,它是當前窗口對象。你在開始時將它定義爲'root',但隨後每次遞歸都會將其自身更改爲第一個窗口,第二個等。 – Blender 2011-06-01 03:56:23

1

如果你可以複製粘貼&代碼那麼您可以出來:

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() 
+0

陰森恐怖。我正要發佈幾乎完全像這樣的東西! – Blender 2011-06-01 04:19:26

相關問題