18

注意:这在某种程度上是对以下问题的跟进:Tkinter - 我什么时候需要调用 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 实际上不会告诉底层解释器已经输入了某些内容,而只是将输入保留在本地,直到它被执行。

4

1 回答 1

13

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

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

(在 Windows 上,您可能必须在 pythonw.exe 而不是 python.exe 中运行脚本,但除此之外,您无需执行任何特殊操作。)


那么它是怎样工作的?最终,诀窍归结为——模块PyOS_InputHook的工作方式相同。readline

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

可能更容易理解它的作用readline它尝试select在标准输入或类似的输入上,循环输入的每个新字符,或每 0.1 秒,或每个信号。

Tkinter做什么是相似的。它更复杂,因为它必须处理 Windows,但在 *nix 上它正在做一些非常类似于readline. 除了它Tcl_DoOneEvent每次都通过循环调用。

这就是关键。重复调用Tcl_DoOneEvent是完全相同的事情mainloop

(当然,线程使一切变得更加复杂,但我们假设您没有创建任何后台线程。在您的真实代码中,如果您想创建后台线程,您将只需要一个线程来处理所有阻塞的Tkinter东西mainloop, 正确的?)


所以,只要你的 Python 代码大部分时间都花在 TTY 输入上(就像交互式解释器通常那样),Tcl 解释器就会继续工作,你的 GUI 就会响应。如果你让 Python 解释器阻塞在 TTY 输入以外的东西上,Tcl 解释器没有运行并且你的 GUI 没有响应。


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

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

您可以select使用 0.02 秒的超时时间(默认输入挂钩使用的超时时间相同),并且t.dooneevent(Tkinter.DONT_WAIT)每次都通过循环调用。

或者,您也可以让 Tk 通过打电话来开车mainloop,但请使用after和朋友来确保您select经常打电话。

于 2014-01-02T21:57:19.767 回答