Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to embed a Python interpreter in a PyQT widget

I want to be able to bring up an interactive python terminal from my python application. Some, but not all, variables in my program needs to be exposed to the interpreter.

Currently I use a sub-classed and modified QPlainTextEdit and route all "commands" there to eval or exec, and keep track of a separate namespace in a dict. However there got to be a more elegant and robust way! How?

Here is an example doing just what I want, but it is with IPython and pyGTK... http://ipython.scipy.org/moin/Cookbook/EmbeddingInGTK

Below is what I currently have. But there are so many corner cases that I probably missed some. It is very slow, try a large print loop... It got to be a simpler and less bug prone way, ...I hope!!

It is the def runCommand(self) function that is the key to understanding my problem. I ideally don't want to improve it, I rather want to replace its content with something simpler and smarter.

The functionality of the console.updateNamespace({'myVar1' : app, 'myVar2' : 1234}) statement in "main" is also important.

import sys, os import traceback from PyQt4 import QtCore from PyQt4 import QtGui  class Console(QtGui.QPlainTextEdit):     def __init__(self, prompt='$> ', startup_message='', parent=None):         QtGui.QPlainTextEdit.__init__(self, parent)         self.prompt = prompt         self.history = []         self.namespace = {}         self.construct = []          self.setGeometry(50, 75, 600, 400)         self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere)         self.setUndoRedoEnabled(False)         self.document().setDefaultFont(QtGui.QFont("monospace", 10, QtGui.QFont.Normal))         self.showMessage(startup_message)      def updateNamespace(self, namespace):         self.namespace.update(namespace)      def showMessage(self, message):         self.appendPlainText(message)         self.newPrompt()      def newPrompt(self):         if self.construct:             prompt = '.' * len(self.prompt)         else:             prompt = self.prompt         self.appendPlainText(prompt)         self.moveCursor(QtGui.QTextCursor.End)      def getCommand(self):         doc = self.document()         curr_line = unicode(doc.findBlockByLineNumber(doc.lineCount() - 1).text())         curr_line = curr_line.rstrip()         curr_line = curr_line[len(self.prompt):]         return curr_line      def setCommand(self, command):         if self.getCommand() == command:             return         self.moveCursor(QtGui.QTextCursor.End)         self.moveCursor(QtGui.QTextCursor.StartOfLine, QtGui.QTextCursor.KeepAnchor)         for i in range(len(self.prompt)):             self.moveCursor(QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor)         self.textCursor().removeSelectedText()         self.textCursor().insertText(command)         self.moveCursor(QtGui.QTextCursor.End)      def getConstruct(self, command):         if self.construct:             prev_command = self.construct[-1]             self.construct.append(command)             if not prev_command and not command:                 ret_val = '\n'.join(self.construct)                 self.construct = []                 return ret_val             else:                 return ''         else:             if command and command[-1] == (':'):                 self.construct.append(command)                 return ''             else:                 return command      def getHistory(self):         return self.history      def setHisory(self, history):         self.history = history      def addToHistory(self, command):         if command and (not self.history or self.history[-1] != command):             self.history.append(command)         self.history_index = len(self.history)      def getPrevHistoryEntry(self):         if self.history:             self.history_index = max(0, self.history_index - 1)             return self.history[self.history_index]         return ''      def getNextHistoryEntry(self):         if self.history:             hist_len = len(self.history)             self.history_index = min(hist_len, self.history_index + 1)             if self.history_index < hist_len:                 return self.history[self.history_index]         return ''      def getCursorPosition(self):         return self.textCursor().columnNumber() - len(self.prompt)      def setCursorPosition(self, position):         self.moveCursor(QtGui.QTextCursor.StartOfLine)         for i in range(len(self.prompt) + position):             self.moveCursor(QtGui.QTextCursor.Right)      def runCommand(self):         command = self.getCommand()         self.addToHistory(command)          command = self.getConstruct(command)          if command:             tmp_stdout = sys.stdout              class stdoutProxy():                 def __init__(self, write_func):                     self.write_func = write_func                     self.skip = False                  def write(self, text):                     if not self.skip:                         stripped_text = text.rstrip('\n')                         self.write_func(stripped_text)                         QtCore.QCoreApplication.processEvents()                     self.skip = not self.skip              sys.stdout = stdoutProxy(self.appendPlainText)             try:                 try:                     result = eval(command, self.namespace, self.namespace)                     if result != None:                         self.appendPlainText(repr(result))                 except SyntaxError:                     exec command in self.namespace             except SystemExit:                 self.close()             except:                 traceback_lines = traceback.format_exc().split('\n')                 # Remove traceback mentioning this file, and a linebreak                 for i in (3,2,1,-1):                     traceback_lines.pop(i)                 self.appendPlainText('\n'.join(traceback_lines))             sys.stdout = tmp_stdout         self.newPrompt()      def keyPressEvent(self, event):         if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):             self.runCommand()             return         if event.key() == QtCore.Qt.Key_Home:             self.setCursorPosition(0)             return         if event.key() == QtCore.Qt.Key_PageUp:             return         elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace):             if self.getCursorPosition() == 0:                 return         elif event.key() == QtCore.Qt.Key_Up:             self.setCommand(self.getPrevHistoryEntry())             return         elif event.key() == QtCore.Qt.Key_Down:             self.setCommand(self.getNextHistoryEntry())             return         elif event.key() == QtCore.Qt.Key_D and event.modifiers() == QtCore.Qt.ControlModifier:             self.close()         super(Console, self).keyPressEvent(event)  welcome_message = '''    ---------------------------------------------------------------      Welcome to a primitive Python interpreter.    --------------------------------------------------------------- '''  if __name__ == '__main__':     app = QtGui.QApplication(sys.argv)     console = Console(startup_message=welcome_message)     console.updateNamespace({'myVar1' : app, 'myVar2' : 1234})     console.show();     sys.exit(app.exec_()) 
like image 337
Mathias Avatar asked May 03 '10 13:05

Mathias


People also ask

Is PyQt a language?

PyQt is a GUI widgets toolkit. It is a Python interface for Qt, one of the most powerful, and popular cross-platform GUI library. PyQt is a blend of Python programming language and the Qt library. This introductory tutorial will assist you in creating graphical applications with the help of PyQt.


2 Answers

Bit late I know, but I recommend the code.InteractiveConsole class: http://docs.python.org/py3k/library/code.html#code.InteractiveConsole

like image 131
Giacomo Avatar answered Oct 11 '22 22:10

Giacomo


You might look into using threads to keep the UI responsive while printing big loops. This would also help keep your tracebacks clean.

Keeping variables in a dict is the way to go – it's what Python itself does internally. As far as exposing “some, but not all” of them, consider just exposing them all. Much easier. If you're concerned about security, beware that you can't reliably hide anything in Python.

As for the hideous cursor/text manipulation: take advantage of the fact that you have a GUI. With a terminal, you just have one “text box”, but in Qt, it might be more appropriate to have a log/result view and a separate command box.

The log view would display the entered commands and results in a read-only textbox.

The command textbox would allow you to enter a command cleanly.

This approach is used in some web frameworks – e.g. via WebError:

enter image description here

like image 20
Petr Viktorin Avatar answered Oct 11 '22 22:10

Petr Viktorin