2013-03-06 233 views
3

我已經在Python Tkinter中編寫了一個應用程序。我最近注意到,對於其中一個操作,如果該操作失敗,它有時會關閉(無任何錯誤)。我已經寫了一個小程序來說明這個問題: -在python tkinter中處理異常

import os 
from Tkinter import * 

def copydir(): 
    src = "D:\\a\\x\\y" 
    dest = "D:\\a\\x\\z" 
    os.rename(src,dest) 

master = Tk() 

def callback(): 
    global master 
    master.after(1, callback) 
    copydir() 
    print "click!" 

b = Button(master, text="OK", command=copydir) 
b.pack() 

master.after(100, callback) 

mainloop() 

要重現該問題,打開它會在「MS命令提示符」重新命名,從而將其改名會拋出異常從Tkinter的代碼的文件夾。

我原來的代碼是使用線程並且正在執行其他任務,所以我試圖使這個測試腳本中的操作儘可能相似。

現在,如果我通過雙擊運行此代碼,那麼程序只需關閉而不會引發任何錯誤。但是如果我從控制檯運行這個腳本,那麼異常消息就會在控制檯上丟失,至少我知道,有些事情是錯誤的。

我可以通過在試圖重命名的代碼中使用try/catch來修復此代碼,但我也想通知用戶有關此故障的信息。所以我只想知道在編寫Tkinter應用程序時應該遵循什麼編碼方法,並且我想知道: -

1)我可以讓我的腳本在文件中轉儲一些堆棧跟蹤,只要用戶通過雙擊運行它。由此至少,我會知道一些錯誤並修復它。

2)我可以防止tkinter應用程序退出這樣的錯誤,並在一些TK對話框中拋出任何異常。

感謝您的幫助!

+0

只是一個通知,你必須聲明全局主只有當你重新分配它,但你只是調用它的方法,所以聲明是不必要的。 – lolopop 2013-03-06 11:48:55

+0

是的,你是對的。不需要將主設備聲明爲全局對象。 – sarbjit 2013-03-06 12:43:17

+0

將您的解決方案發布爲答案,而不是將其添加到問題中。 – 2013-03-06 17:46:15

回答

4

您可以覆蓋Tkinter的CallWrapper,解釋爲here。這是必要的,爲了做到這一點使用的Tkinter的命名,而不是進口通配符導入的:

import Tkinter as tk 
import traceback 

class Catcher: 
    def __init__(self, func, subst, widget): 
     self.func = func 
     self.subst = subst 
     self.widget = widget 
    def __call__(self, *args): 
     try: 
      if self.subst: 
       args = apply(self.subst, args) 
      return apply(self.func, args) 
     except SystemExit, msg: 
      raise SystemExit, msg 
     except: 
      traceback.print_exc(file=open('test.log', 'a')) 

# ... 
tk.CallWrapper = Catcher 
b = tk.Button(master, text="OK", command=copydir) 
b.pack() 
master.mainloop() 
+0

我已經在我的代碼中包含了這個,但是它似乎並沒有工作。雙擊代碼時,它會直接退出而不寫任何文件。查看我更新的代碼。 – sarbjit 2013-03-06 14:50:44

+0

@sarbjit我沒有嘗試上面的代碼,但它似乎只在導入命名時才起作用。我已經更新了答案。 – 2013-03-06 18:47:24

+0

使用命名導入,它工作正常。雖然我不得不添加一些代碼來取消事件後的所有未決Tk並明確關閉主循環。 – sarbjit 2013-03-07 05:28:23

2

我不是很肯定,如果我明白你很好,但這個簡單的代碼,使您在該目錄中無法找到的情況下控制:

import os 
from Tkinter import * 

def copydir(): 
    src = "D:\\troll" 
    dest = "D:\\trollo" 

    try: 
     os.rename(src, dest) 
    except: 
     print 'Sorry, I couldnt rename' 
     # optionally: raise YourCustomException 
     # or use a Tkinter popup to let the user know 

master = Tk() 

b = Button(master, text="OK", command=copydir) 
b.pack() 

mainloop() 

編輯:既然你想一般方法和Tkinter不傳播異常,你必須編寫它。有兩種方法:

1)硬編碼到的功能,因爲我在上面(可怕的)

2)的例子一樣使用裝飾添加一個try-except塊。

import os 
from Tkinter import * 


class ProvideException(object): 
    def __init__(self, func): 
     self._func = func 

    def __call__(self, *args): 

     try: 
      return self._func(*args) 

     except Exception, e: 
      print 'Exception was thrown', str(e) 
      # Optionally raise your own exceptions, popups etc 


@ProvideException 
def copydir(): 
    src = "D:\\troll" 
    dest = "D:\\trollo" 

    os.rename(src, dest) 

master = Tk() 

b = Button(master, text="OK", command=copydir) 
b.pack() 

mainloop() 

編輯:如果要包括堆棧

include traceback 

,並在不同的塊:

except Exception, e: 
    print 'Exception was thrown', str(e) 
    print traceback.print_stack() 

的解決方案,A.Rodas提出是更清潔和更完整,但是,更復雜的理解。

+0

我實際上是在尋找一種通用解決方案來處理Tkinter應用程序中的異常。就像在這種情況下,我知道這個解決方案。我想知道的是,如果在Python Tkinter應用程序中引發的任何異常有可能被轉儲到文本文件中。 – sarbjit 2013-03-06 12:42:46

+0

@sarbjit我擴展了我的解決方案。檢查出來,現在我明白你想要什麼,我很確定它會有幫助。 – bgusach 2013-03-06 13:34:29

+0

感謝您的解決方案,這就是我正在尋找的。由於我在學習Python,因此我沒有多少懷疑,並希望你能幫助澄清: - 1)當你在代碼中使用'@ ProvideException'時,這意味着什麼,我們在Python中怎麼稱呼它(我想讀一下它)。 2)通過這種方法,是否有可能轉儲完整的堆棧跟蹤。所以我希望每當發生任何異常時,都應該將其轉儲到文本文件中。截至目前,它只顯示字符串錯誤信息。這樣做也會處理所有類型的異常,對嗎? – sarbjit 2013-03-06 13:51:16

0

您可以使用except-hook()爲異常安裝全局處理程序。 可以找到一個示例here

5

我看到你有一個非面向對象的例子,所以我會表現出兩種變體來解決問題的異常捕捉。

關鍵在tkinter\__init__.py文件中。我們可以看到有一個類似Tk類的文檔化方法report_callback_exception。下面是它的描述:在sys.stderr

report_callback_exception()

報告回調例外。

應用程序可能希望覆蓋此內部函數,並且應該在sys.stderr爲None時。

因此,我們看到它應該重寫此方法,讓我們做到這一點!

非面向對象的解決方案

import tkinter as tk 
from tkinter.messagebox import showerror 


if __name__ == '__main__': 

    def bad(): 
     raise Exception("I'm Bad!") 

    # any name as accepted but not signature 
    def report_callback_exception(self, exc, val, tb): 
     showerror("Error", message=str(val)) 

    tk.Tk.report_callback_exception = report_callback_exception 
    # now method is overridden 

    app = tk.Tk() 
    tk.Button(master=app, text="bad", command=bad).pack() 
    app.mainloop() 

面向對象的解決方案

import tkinter as tk 
from tkinter.messagebox import showerror 


class Bad(tk.Tk): 

    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 
     # or tk.Tk.__init__(*args, **kwargs) 

     def bad(): 
      raise Exception("I'm Bad!") 
     tk.Button(self, text="bad", command=bad).pack() 

    def report_callback_exception(self, exc, val, tb): 
     showerror("Error", message=str(val)) 

if __name__ == '__main__': 

    app = Bad() 
    app.mainloop() 

The result

我的環境:

Python 3.5.1 |Anaconda 2.4.1 (64-bit)| (default, Dec 7 2015, 15:00:12) [MSC 
v.1900 64 bit (AMD64)] on win32 

tkinter.TkVersion 
8.6 

tkinter.TclVersion 
8.6 
+0

如果要報告錯誤的完整回溯,請使用'message = traceback.format_exc()'([credit to James](https://stackoverflow.com/a/37493588/3357935)) – 2018-03-01 21:17:47