如何使用tkinter在python中嵌入python解释器框架? [英] How can I embed a python interpreter frame in python using tkinter?

查看:92
本文介绍了如何使用tkinter在python中嵌入python解释器框架?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将控制终端窗口小部件添加到我的纯python + tkinter应用程序中,类似于Blender中提供的python解释器。它应该在相同的上下文(进程)中运行,以便用户可以添加功能并通过控件小部件控制当前正在运行的应用程序。理想情况下,我希望它也劫持当前应用程序的stdout和stderr,以便它会报告正在运行的应用程序中的任何问题或调试信息。

I want to add a control terminal widget to my pure python+tkinter application similar to the python interpreter provided in Blender. It should be running within the same context (process) so the user can add features and control the application that is currently running from the control widget. Ideally I'd like it to also "hijack" stdout and stderr of the current application so it will report any problems or debugging information within the running application.

到目前为止,我已经提出了。唯一的问题是它不响应命令,并且当用户关闭窗口时线程也不会停止。

This is what I have come up with so far. The only problems are that it isn't responding to commands, and the thread doesn't stop when the user closes the window.

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


推荐答案

我有万一有人还在乎的答案! (我也更改为python 3,因此 import tkinter 而不是 import tkinter

I have the answer in case anyone still cares! (I have also changed to python 3, hence the import tkinter rather than import Tkinter)

我通过使用一个单独的文件运行 InteractiveConsole ,然后将主文件打开了另一个方法,对原始方法进行了一些更改。子进程中的文件(我称为console.py,位于相同目录中),将该子进程的stdout,stderr和stdin以编程方式链接到tkinter Text小部件。

I have changed the approach slightly from the original by using a separate file to run the InteractiveConsole, and then making the main file open this other file (which I have called console.py and is in the same directory) in a subprocess, linking the stdout, stderr, and stdin of this subprocess to the tkinter Text widget programatically.

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

Here is the code in the for the console file (if this is run normally, it acts like a normal console):

# console.py
import code

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

这是python解释器的代码,它在Text小部件内运行控制台:

And here is the code for the python interpreter, that runs the console inside the Text widget:

# main.py
import tkinter as tk
import subprocess
import queue
import os
from threading import Thread

class Console(tk.Frame):
    def __init__(self,parent=None):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.createWidgets()

        # get the path to the console.py file assuming it is in the same folder
        consolePath = os.path.join(os.path.dirname(__file__),"console.py")
        # open the console.py file (replace the path to python with the correct one for your system)
        # e.g. it might be "C:\\Python35\\python"
        self.p = subprocess.Popen(["python3",consolePath],
                                  stdout=subprocess.PIPE,
                                  stdin=subprocess.PIPE,
                                  stderr=subprocess.PIPE)

        # make queues for keeping stdout and stderr whilst it is transferred between threads
        self.outQueue = queue.Queue()
        self.errQueue = queue.Queue()

        # keep track of where any line that is submitted starts
        self.line_start = 0

        # make the enter key call the self.enter function
        self.ttyText.bind("<Return>",self.enter)

        # a daemon to keep track of the threads so they can stop running
        self.alive = True
        # start the functions that get stdout and stderr in separate threads
        Thread(target=self.readFromProccessOut).start()
        Thread(target=self.readFromProccessErr).start()

        # start the write loop in the main thread
        self.writeLoop()

    def destroy(self):
        "This is the function that is automatically called when the widget is destroyed."
        self.alive=False
        # write exit() to the console in order to stop it running
        self.p.stdin.write("exit()\n".encode())
        self.p.stdin.flush()
        # call the destroy methods to properly destroy widgets
        self.ttyText.destroy()
        tk.Frame.destroy(self)
    def enter(self,e):
        "The <Return> key press handler"
        string = self.ttyText.get(1.0, tk.END)[self.line_start:]
        self.line_start+=len(string)
        self.p.stdin.write(string.encode())
        self.p.stdin.flush()

    def readFromProccessOut(self):
        "To be executed in a separate thread to make read non-blocking"
        while self.alive:
            data = self.p.stdout.raw.read(1024).decode()
            self.outQueue.put(data)

    def readFromProccessErr(self):
        "To be executed in a separate thread to make read non-blocking"
        while self.alive:
            data = self.p.stderr.raw.read(1024).decode()
            self.errQueue.put(data)

    def writeLoop(self):
        "Used to write data from stdout and stderr to the Text widget"
        # if there is anything to write from stdout or stderr, then write it
        if not self.errQueue.empty():
            self.write(self.errQueue.get())
        if not self.outQueue.empty():
            self.write(self.outQueue.get())

        # run this method again after 10ms
        if self.alive:
            self.after(10,self.writeLoop)

    def write(self,string):
        self.ttyText.insert(tk.END, string)
        self.ttyText.see(tk.END)
        self.line_start+=len(string)

    def createWidgets(self):
        self.ttyText = tk.Text(self, wrap=tk.WORD)
        self.ttyText.pack(fill=tk.BOTH,expand=True)


if __name__ == '__main__':
    root = tk.Tk()
    root.config(background="red")
    main_window = Console(root)
    main_window.pack(fill=tk.BOTH,expand=True)
    root.mainloop()

从stdout和stderr读取的原因是在单独的线程中因为read方法处于阻塞状态,这导致程序冻结,直到console.py子进程提供更多输出为止,除非这些输出位于单独的线程中。由于tkinter不是线程安全的,因此需要writeLoop方法和队列来写入Text小部件。

The reason that reading from stdout and stderr is in separate threads is because the read method is blocking, which causes the program to freeze until the console.py subprocess gives more output, unless these are in separate threads. The writeLoop method and the queues are needed to write to the Text widget since tkinter is not thread safe.

这当然仍然有待解决的问题,例如事实文本小部件上的任何代码即使已经提交也可以编辑,但是希望它能回答您的问题。

This certainly still has problems to be ironed out, such as the fact that any code on the Text widget is editable even once already submitted, but hopefully it answers your question.

编辑:我还消除了一些tkinter,以便控制台的行为更像是标准的小部件。

I've also neatened some of the tkinter such that the Console will behave more like a standard widget.

这篇关于如何使用tkinter在python中嵌入python解释器框架?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆