什么魔法阻止Tkinter程序在交互式shell中阻塞?

时间:2014-01-02 20:56:50

标签: python tkinter interactive-shell

注意:这有点是对问题的跟进:Tkinter - when do I need to call mainloop?

通常在使用Tkinter时,您可以调用Tk.mainloop来运行事件循环,并确保事件得到正确处理,并且窗口保持交互状态而不会阻塞。

在交互式shell中使用Tkinter时,似乎没有必要运行主循环。举个例子:

>>> import tkinter
>>> t = tkinter.Tk()

将出现一个窗口,它不会阻止:您可以与它进行交互,拖动它并关闭它。

因此,交互式shell中的某些内容似乎确实创建了一个窗口,并在后台运行事件循环。

现在有趣的事情。再次从上面举例,但是在下一个提示中(不关闭窗口),输入任何内容 - 而不是实际执行它(即不要按回车键)。例如:

>>> t = tkinter.Tk()
>>> print('Not pressing enter now.') # not executing this

如果您现在尝试与Tk窗口进行交互,您将看到它完全阻止。因此,当我们向交互式shell输入命令时,我们认为将在后台运行的事件循环停止。如果我们发送输入的命令,您将看到事件循环继续,我们在阻止期间所做的任何操作都将继续处理。

所以最大的问题是:交互式shell中发生的这种魔法是什么?当我们没有明确地执行它时,主循环运行的是什么?当我们输入命令时(而不是在我们执行它们时停止),为什么需要暂停?

注意:上面的命令行解释器就是这样,而不是IDLE。对于IDLE,我假设GUI实际上并不会告诉底层解释器已经输入了某些内容但只是将输入保存在本地,直到它被执行为止。

1 个答案:

答案 0 :(得分:9)

实际上,这并不是一个重要的交互式解释器,而是等待TTY的输入。您可以从这样的脚本中获得相同的行为:

import tkinter
t = tkinter.Tk()
input()

(在Windows上,您可能必须在pythonw.exe而不是python.exe中运行脚本,否则,您不必执行任何特殊操作。)


那么,它是如何运作的?最终,诀窍归结为PyOS_InputHook - 与readline模块的工作方式相同。

如果stdin是TTY,那么,每次它尝试获取input()的行,code模块的各个位,内置的REPL等,Python调用任何已安装的PyOS_InputHook而不是仅仅从stdin读取。

理解what readline does可能更容易:它在stdin或类似物上尝试select,为每个新输入字符循环,或每0.1秒或每个信号循环。

What Tkinter does类似。它更复杂,因为它必须处理Windows,但在* nix上,它正在做与readline非常相似的事情。除非它每次都在循环中调用Tcl_DoOneEvent

这就是关键。重复调用Tcl_DoOneEventmainloop完全相同。

(当然,线程使一切变得更复杂,但是我们假设你没有创建任何后台线程。在你的真实代码中,如果你想创建后台线程,你将只有一个线程用于所有{{无论如何,阻止Tkinter的东西,对吗?)


因此,只要您的Python代码大部分时间都被阻止在TTY输入上(通常是交互式解释器),Tcl解释器就会随之而来,并且您的GUI正在响应。如果你在TTY输入以外的地方制作Python解释器块,那么Tcl解释器没有运行,你的GUI没有响应。


如果您想在纯Python代码中手动执行相同的操作,该怎么办?如果您想要将Tkinter GUI和基于mainloop的网络客户端集成到单线程应用程序中,那么您需要这样做吗?

这很简单:从另一个驱动一个循环。

你可以select超时0.02s(默认输入挂钩使用相同的超时),并且每次循环都调用select

或者,您可以通过拨打t.dooneevent(Tkinter.DONT_WAIT)让Tk开车,但使用mainloop和朋友确保您经常拨打after