通过GUI重定向标准输入

2 投票
1 回答
1905 浏览
提问于 2025-04-17 22:36

我有一个非常简单的应用程序,它的目的是在一个 textEdit 组件中评估给定的 Python 代码,并在另一个组件中显示结果。

import sys                                                                                                                                                                                      
from PyQt4 import QtGui
from cStringIO import StringIO

class SampleGUI(QtGui.QWidget):
    def __init__(self):
        super(SampleGUI, self).__init__()
        self.initGUI()

    def initGUI(self):
        self.code = QtGui.QTextEdit()
        self.result = QtGui.QTextEdit()

        btn = QtGui.QPushButton('Evaluate')
        btn.clicked.connect(self.evaluate)

        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(self.code)
        vbox.addWidget(btn)
        vbox.addWidget(self.result)

        self.setLayout(vbox)
        self.show()

    def evaluate(self):
        source_code = str(self.code.toPlainText())
        old_stdout = sys.stdout
        redirected_output = sys.stdout = StringIO()
        exec source_code
        sys.stdout = old_stdout
        self.result.setText(redirected_output.getvalue())

def main():
    app = QtGui.QApplication([])
    s = SampleGUI()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

到目前为止,这个功能运行得很好,只要你不需要输入值。比如说,你从控制台运行这个脚本,输入 python samplegui.py,如果你在第一个文本框中输入类似这样的内容:

a = 3
print 5 + a
b = input()
print 1 + b

然后点击 Evaluate,这个应用程序会期待你在控制台中输入一个值。你输入一些值并按下回车后,它就会继续并正确评估。

我想要实现的是将 stdin 重定向到一个我可以在 GUI 内部操作的组件。

我该怎么做呢?理想情况下,我希望能像在 Python 解释器中那样,输入和输出是互动的。另一种选择是以某种方式处理或捕捉当应用程序等待输入时的信号,并显示一个输入对话框。

编辑:

结果发现这比我想的要简单。这里你可以看到一个版本的脚本,它用 QInputDialog 拦截了 sys.stdin,这样用户就可以从 GUI 输入值。

import sys 
from PyQt4 import QtGui
from cStringIO import StringIO

class InputGUI():
    def __init__(self, parentWidget):
        self.parentWidget = parentWidget

    def readline(self):
        text, ok = QtGui.QInputDialog.getText(self.parentWidget, 'Introduce value', 'Value:')
        if ok: 
            return str(text)
        else:
            return ''

class SampleGUI(QtGui.QWidget):
    def __init__(self):
        super(SampleGUI, self).__init__()
        self.initGUI()

    def initGUI(self):
        self.code = QtGui.QTextEdit()
        self.result = QtGui.QTextEdit()

        btn = QtGui.QPushButton('Evaluate')
        btn.clicked.connect(self.evaluate)

        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(self.code)
        vbox.addWidget(btn)
        vbox.addWidget(self.result)

        self.setLayout(vbox)
        self.show()

    def evaluate(self):
        source_code = str(self.code.toPlainText())
        streams = sys.stdin, sys.stdout
        sys.stdin = InputGUI(self)                                                                                                                                                              
        redirected_output = sys.stdout = StringIO()
        exec source_code
        sys.stdin, sys.stdout = streams
        self.result.setText(redirected_output.getvalue())

def main():
    app = QtGui.QApplication([])
    s = SampleGUI()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

1 个回答

3

我会把 sys.stdin 替换成一个自定义类的实例,这个类需要实现文件描述符的协议。这样一来,当用户想通过 input()sys.stdin.readline() 读取内容时,你就可以决定应该发生什么。

至于如何实现文件描述符,你需要实现文档中提到的所有方法,具体可以参考 这份文档。另外,你可能还想实现 上下文管理器协议,这样你的自定义 sys.stdin 就可以和 with 语句一起使用。

不过要注意,如果用户自己再次替换 sys.stdin,还是有可能出问题,但至少这种情况比有人调用 input() 更不容易发生。

撰写回答