如何使用tkinter在python中嵌入python解释器框架?

2024-09-30 05:16:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我想在纯python+tkinter应用程序中添加一个控制终端小部件,类似于Blender中提供的python解释器。它应该在相同的上下文(进程)中运行,这样用户就可以添加特性并控制当前从控件小部件运行的应用程序。理想情况下,我希望它也“劫持”当前应用程序的stdout和stderr,这样它就可以报告正在运行的应用程序中的任何问题或调试信息。在

这就是我到目前为止的想法。唯一的问题是它不响应命令,并且当用户关闭窗口时线程不会停止。在

import Tkinter as tk
import sys
import code
from threading import *

class Console(tk.Frame):
    def __init__(self,parent=None):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        sys.stdout = self
        sys.stderr = self
        self.createWidgets()
        self.consoleThread = ConsoleThread()
        self.after(100,self.consoleThread.start)

    def write(self,string):
        self.ttyText.insert('end', string)
        self.ttyText.see('end')

    def createWidgets(self):
        self.ttyText = tk.Text(self.parent, wrap='word')
        self.ttyText.grid(row=0,column=0,sticky=tk.N+tk.S+tk.E+tk.W)


class ConsoleThread(Thread):

    def __init__(self):
        Thread.__init__(self)

    def run(self):
        vars = globals().copy()
        vars.update(locals())
        shell = code.InteractiveConsole(vars)
        shell.interact()

if __name__ == '__main__':
    root = tk.Tk()
    root.config(background="red")
    main_window = Console(root)
    main_window.mainloop()
    try:
        if root.winfo_exists():
            root.destroy()
    except:
        pass

Tags: 用户importself应用程序initmain部件def
2条回答

it isn't responding to commands

它不响应命令的原因是您没有将Text小部件(self.ttyText)链接到stdin。当前,当你输入时,它会将文本添加到小部件中,而不添加其他内容。这种链接可以类似于您已经使用stdoutstderr完成的操作。在

在实现这一点时,您需要跟踪小部件中文本的哪一部分是用户输入的文本—这可以使用标记(as described here)来完成。在

the thread doesn't stop when the user closes the window.

我不认为没有一个“干净”的方法来解决这个问题而不需要重写主要的代码,但是一个似乎足够好的解决方案是它只需检测小部件何时被破坏并将字符串"\n\nexit()"写入解释器。这将调用解释器中的exit函数,这将导致对shell.interact的调用完成,从而使线程完成。在

在这里,不使用ado对代码进行了进一步修改:

import tkinter as tk
import sys
import code
from threading import Thread
import queue


class Console(tk.Frame):
    def __init__(self, parent, _locals, exit_callback):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.exit_callback = exit_callback
        self.destroyed = False

        self.real_std_in_out = (sys.stdin, sys.stdout, sys.stderr)

        sys.stdout = self
        sys.stderr = self
        sys.stdin = self

        self.stdin_buffer = queue.Queue()

        self.createWidgets()

        self.consoleThread = Thread(target=lambda: self.run_interactive_console(_locals))
        self.consoleThread.start()

    def run_interactive_console(self, _locals):
        try:
            code.interact(local=_locals)
        except SystemExit:
            if not self.destroyed:
                self.after(0, self.exit_callback)

    def destroy(self):
        self.stdin_buffer.put("\n\nexit()\n")
        self.destroyed = True
        sys.stdin, sys.stdout, sys.stderr = self.real_std_in_out
        super().destroy()

    def enter(self, event):
        input_line = self.ttyText.get("input_start", "end")
        self.ttyText.mark_set("input_start", "end-1c")
        self.ttyText.mark_gravity("input_start", "left")
        self.stdin_buffer.put(input_line)

    def write(self, string):
        self.ttyText.insert('end', string)
        self.ttyText.mark_set("input_start", "end-1c")
        self.ttyText.see('end')

    def createWidgets(self):
        self.ttyText = tk.Text(self.parent, wrap='word')
        self.ttyText.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)
        self.ttyText.bind("<Return>", self.enter)
        self.ttyText.mark_set("input_start", "end-1c")
        self.ttyText.mark_gravity("input_start", "left")

    def flush(self):
        pass

    def readline(self):
        line = self.stdin_buffer.get()
        return line


if __name__ == '__main__':
    root = tk.Tk()
    root.config(background="red")
    main_window = Console(root, locals(), root.destroy)
    main_window.mainloop()

除了解决问题中所述问题的代码外,此代码几乎没有更改。在

与我之前的回答相比,这段代码的优点是它可以在单个进程中工作,因此可以在应用程序的任何一点创建,从而给程序员更多的控制权。在

{I不应该有一个基本的文本输出}和其他可编辑文本}的输出

我有答案,以防有人还在乎!(我还改为python3,因此import tkinter而不是{})

与原来的方法略有不同,我使用一个单独的文件来运行InteractiveConsole,然后让主文件打开另一个文件(我调用了这个文件控制台.py并位于子进程的同一目录下),链接stdout、stderr,并将此子进程的stdin程序化到tkinter文本小部件。在

以下是中用于控制台文件的代码(如果正常运行,则其作用类似于普通控制台):

# console.py
import code

if __name__ == '__main__':
    vars = globals().copy()
    vars.update(locals())
    shell = code.InteractiveConsole(vars)
    shell.interact() 

下面是python解释器的代码,它在文本小部件中运行控制台:

^{pr2}$

从stdout和stderr读取的原因是由于read方法阻塞,这会导致程序冻结,直到控制台.py子进程提供更多的输出,除非这些输出在单独的线程中。因为tkinter不是线程安全的,所以需要writeLoop方法和队列来写入文本小部件。在

这当然还有一些问题需要解决,比如文本小部件上的任何代码即使已经提交也可以编辑,但希望它能回答您的问题。在

编辑:我还整理了一些tkinter,使控制台的行为更像一个标准的小部件。在

相关问题 更多 >

    热门问题