我在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 App时应该遵循哪些编码方法,我想知道: -
1)每当用户双击它时,我可以让我的脚本在文件中转储一些堆栈跟踪。通过这个至少,我会知道一些错误并修复它。
2)我是否可以阻止tkinter应用程序退出此类错误并在某些TK对话框中抛出任何异常。
感谢您的帮助!!
答案 0 :(得分:7)
我看到你有一个非面向对象的例子,所以我将展示两个变种来解决异常捕获问题。
密钥位于tkinter\__init__.py
文件中。可以看出,report_callback_exception
类有一个记录的方法Tk
。这是它的描述:
report_callback_exception()
在sys.stderr上报告回调异常。
应用程序可能希望覆盖此内部函数,并且应该在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()
我的环境:
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
答案 1 :(得分:4)
您可以覆盖Tkinter的CallWrapper
。有必要使用命名的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()
答案 2 :(得分: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块中:
except Exception, e:
print 'Exception was thrown', str(e)
print traceback.print_stack()
A.Rodas提出的解决方案更清洁,更完整,但理解起来更复杂。
答案 3 :(得分:0)
您可以使用except-hook()为异常安装全局处理程序。 可以找到一个示例here。