Я хочу смочь поднять интерактивный терминал Python из своего приложения Python. Некоторые, но не все, переменные в моей программе должны быть подвергнуты интерпретатору.
В настоящее время я использую разделенный на подклассы и измененный QPlainTextEdit
и направьте все "команды" там к eval
или exec
, и отслеживайте отдельное пространство имен в dict. Однако там добрался, чтобы быть более изящным и устойчивым путем! Как?
Вот пример, делающий, что я хочу, но это - с IPython и pyGTK... http://ipython.scipy.org/moin/Cookbook/EmbeddingInGTK
Ниже то, что я в настоящее время имею. Но существует столько угловых случаев, что я, вероятно, скучал по некоторым. Это очень медленно, попробуйте цикл с крупным шрифтом... Это добралось, чтобы быть более простым и меньшей ошибкой склонный путь... Я надеюсь!!
Это def runCommand(self)
функция, которая является ключом к пониманию моей проблемы. Я идеально не хочу улучшать его, я скорее хочу заменить его содержание чем-то более простым и более умным.
Функциональность console.updateNamespace({'myVar1' : app, 'myVar2' : 1234})
оператор в "основном" также важен.
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_())
Не уверены, что именно вы хотите, но пытались сохранить содержимое виджета во временный файл и передать его стандартному интерпретатору Python с помощью Popen?
Документ находится здесь: http://docs.python.org/release/2.6.5/library/subprocess.html#subprocess.Popen
Пример:
import tempfile, os, sys, subprocess
# get the code
code = get_widget_content()
# save the code to a temporary file
file_handle, file_path = tempfile.mkstemp()
tmp_file = os.fdopen(file_handle, 'w')
tmp_file.write(code)
tmp_file.close()
#execute it
p = subprocess.Popen([sys.executable, file_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# wait for the command to complete
p.wait()
# retrieve the output:
pyerr = p.stderr.readlines()
pyout = p.stdout.readlines()
# do what ever you want with it
print(pyerr)
print(pyout)
Вы можете рассмотреть возможность использования потоков, чтобы сохранить отзывчивость пользовательского интерфейса при печати больших циклов. Это также поможет сохранить чистоту трассировки.
Хранить переменные в дикте - это то, что нужно - так делает сам Python. Что касается раскрытия "некоторых, но не всех" из них, подумайте о том, чтобы просто раскрыть их все. Это намного проще. Если вы беспокоитесь о безопасности, имейте в виду, что в Python невозможно надежно спрятать что-либо.
Что касается ужасных манипуляций с курсором/текстом: воспользуйтесь тем, что у вас есть графический интерфейс. В терминале у вас есть только одно "текстовое поле", но в Qt, возможно, было бы более уместно иметь представление журнала/результатов и отдельное командное поле.
Представление журнала будет отображать введенные команды и результаты в текстовом поле, доступном только для чтения.
Текстовое поле команды позволяет вводить команду в чистом виде.
Этот подход используется в некоторых веб-фреймворках - например, через WebError:
Похоже, вы сделали что-то похожее на мое приложение Veusz, https://veusz.github.io/. Я подумал, что вам будет полезно увидеть более полную реализацию. Я не могу публиковать гиперссылки, но посмотрите в windows/consolewindow.py класс виджета. Команды выполняются классом document/commandinterpreter.py. Интерфейс определен в document/commandinterface.py. Однако в основном это делается с помощью диктанта.