注意:这是对以下问题的跟进: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 实际上不会告诉底层解释器已经输入了某些内容,而只是将输入保留在本地,直到它被执行。
最佳答案
实际上在这里重要的不是交互式解释器,而是等待 TTY 上的输入。您可以从这样的脚本中获得相同的行为:
import tkinter
t = tkinter.Tk()
input()
(在 Windows 上,您可能必须在 pythonw.exe 而不是 python.exe 中运行脚本,但除此之外,您无需执行任何特殊操作。)
那么,它是如何工作的呢?最终,技巧归结为 PyOS_InputHook
——与 readline
模块的工作方式相同。
如果 stdin 是一个 TTY,那么,每次它尝试使用 input()
获取一行时,code
模块的各个位,内置的 REPL等等,Python 调用任何已安装的 PyOS_InputHook
而不是仅仅从标准输入读取。
可能更容易理解what readline
does : 它尝试在 stdin 或类似的设备上select
,循环输入每个新字符,或每 0.1 秒,或每个信号。
What Tkinter
does很相似。它更复杂,因为它必须处理 Windows,但在 *nix 上它做的事情与 readline
非常相似。除了每次循环都调用 Tcl_DoOneEvent
。
这就是关键。重复调用 Tcl_DoOneEvent
与 mainloop
完全相同
(线程使一切变得更复杂,当然,但我们假设您没有创建任何后台线程。在您的实际代码中,如果您想要创建后台线程,您将只为所有 Tkinter
阻塞在 mainloop
上的东西,对吧?)
因此,只要您的 Python 代码将大部分时间花在 TTY 输入上(就像交互式解释器通常那样),Tcl 解释器就会继续运行并且您的 GUI 会做出响应。如果您使 Python 解释器阻塞 TTY 输入以外的其他内容,则 Tcl 解释器不会运行并且您的 GUI 不会响应。
如果您想在纯 Python 代码中手动执行相同的操作怎么办?如果您想要将 Tkinter GUI 和基于 select
的网络客户端集成到单线程应用程序中,您需要这样做,对吗?
这很简单:从另一个循环驱动一个循环。
您可以选择
0.02 秒的超时(默认输入 Hook 使用的相同超时),并每次调用 t.dooneevent(Tkinter.DONT_WAIT)
循环。
或者,您可以通过调用 mainloop
让 Tk 驱动,但是使用 after
和 friend 来确保您足够频繁地调用 select
.
关于python - 是什么魔法阻止了 Tkinter 程序在交互式 shell 中阻塞?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20891710/