本文介绍了有什么魔术可以阻止Tkinter程序在交互式shell中阻塞?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:这是对该问题的后续跟进:

通常在使用 Tkinter 时,您将 Tk.mainloop 以运行事件循环并确保事件得到正确处理并保留窗口互动而不受阻碍.

Usually when using Tkinter, you call Tk.mainloop to run the event loop and ensure that events are properly processed and windows remain interactive without blocking.

在交互式外壳中使用Tkinter时,似乎不需要运行主循环.举个例子:

When using Tkinter from within an interactive shell, running the main loop does not seem necessary. Take this example:

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

将出现一个窗口,并且该窗口不会被阻止:您可以与之交互,拖动它并关闭它.

A window will appear, and it will not block: You can interact with it, drag it around, and close it.

因此,交互式外壳中的某些内容似乎可以识别出已创建一个窗口并在后台运行事件循环.

So, something in the interactive shell does seem to recognize that a window was created and runs the event loop in the background.

现在开始有趣的事情.再次以上面的示例为例,但是在下一个提示(不关闭窗口)中,输入任何内容,而无需实际执行(即,不要按enter键).例如:

Now for the interesting thing. Take the example from above again, but then in the next prompt (without closing the window), enter anything—without actually executing it (i.e. don’t press enter). For example:

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

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

If you now try to interact with the Tk window, you will see that it completely blocks. So the event loop which we thought would be running in the background stopped while we were entering a command to the interactive shell. If we send the entered command, you will see that the event loop continues and whatever we did during the blocking will continue to process.

因此,最大的问题是:在交互式外壳中发生的魔术是什么?当我们没有明确进行操作时,是什么在主循环中运行?为什么在我们输入命令时需要将其暂停(而不是在执行命令时将其暂停)?

So the big question is: What is this magic that happens in the interactive shell? What runs the main loop when we are not doing it explicitly? And why does it need to halt when we enter commands (instead of halting when we execute them)?

注意:上面的内容在命令行解释器(而不是IDLE)中是这样工作的.至于IDLE,我认为GUI不会实际上告诉底层解释器已经输入了某些内容,而只是将输入保留在本地直到执行完毕.

Note: The above works like this in the command line interpreter, not IDLE. As for IDLE, I assume that the GUI won’t actually tell the underlying interpreter that something has been entered but just keep the input locally around until it’s being executed.

推荐答案

实际上,它并不是交互式的解释器,而是在等待TTY上的输入.您可以从如下脚本中获得相同的行为:

It's actually not being an interactive interpreter that matters here, but waiting for input on a TTY. You can get the same behavior from a script like this:

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

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

(On Windows, you may have to run the script in pythonw.exe instead of python.exe, but otherwise, you don't have to do anything special.)

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

So, how does it work? Ultimately, the trick comes down to PyOS_InputHook—the same way the readline module works.

如果stdin是TTY,则每次它尝试使用input()code模块的各个位,内置REPL等来获取一行时,Python都会调用任何已安装的PyOS_InputHook只是从标准输入中读取.

If stdin is a TTY, then, each time it tries to fetch a line with input(), various bits of the code module, the built-in REPL, etc., Python calls any installed PyOS_InputHook instead of just reading from stdin.

readline的作用可能更容易理解:它会尝试到select到stdin或类似的地方,为输入的每个新字符或每0.1秒或每个信号循环一次.

It's probably easier to understand what readline does: it tries to select on stdin or similar, looping for each new character of input, or every 0.1 seconds, or every signal.

Tkinter的作用是相似的.因为它必须处理Windows,所以它更加复杂,但是在* nix上,它执行的操作与readline类似.除了它每次通过循环调用Tcl_DoOneEvent.

What Tkinter does is similar. It's more complicated because it has to deal with Windows, but on *nix it's doing something pretty similar to readline. Except that it's calling Tcl_DoOneEvent each time through the loop.

这就是关键.重复调用Tcl_DoOneEvent mainloop 完全相同.

And that's the key. Calling Tcl_DoOneEvent repeatedly is exactly the same thing that mainloop does.

(当然,线程会使一切变得更加复杂,但让我们假设您尚未创建任何后台线程.在您的实际代码中,如果要创建后台线程,则只为所有仍然在mainloop上阻止的内容,对吧?)

(Threads make everything more complicated, of course, but let's assume you haven't created any background threads. In your real code, if you want to create background threads, you'll just have a thread for all the Tkinter stuff that blocks on mainloop anyway, right?)

因此,只要您的Python代码将大部分时间都花在了TTY输入上(通常是交互式解释器),则Tcl解释器会不断变化,而GUI也会响应.如果您在除TTY输入之外的其他位置上使Python解释器发生阻塞,则Tcl解释器未运行,并且您的GUI也没有响应.

So, as long as your Python code is spending most of its time blocked on TTY input (as the interactive interpreter usually is), the Tcl interpreter is chugging along and your GUI is responding. If you make the Python interpreter block on something other than TTY input, the Tcl interpreter is not running and the your GUI is not responding.

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

What if you wanted to do the same thing manually in pure Python code? You'd of need to do that if you want to, e.g., integrate a Tkinter GUI and a select-based network client into a single-threaded app, right?

这很容易:驱使另一个循环.

That's easy: Drive one loop from the other.

您可以select的超时时间为0.02s(与默认输入挂钩相同的超时时间),并在每次循环中调用t.dooneevent(Tkinter.DONT_WAIT).

You can select with a timeout of 0.02s (the same timeout the default input hook uses), and call t.dooneevent(Tkinter.DONT_WAIT) each time through the loop.

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

Or, alternatively, you can let Tk drive by calling mainloop, but use after and friends to make sure you call select often enough.

这篇关于有什么魔术可以阻止Tkinter程序在交互式shell中阻塞?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

11-02 08:45