2010-05-03 7 views
24

Я хочу, чтобы иметь возможность запускать интерактивный терминал python из моего приложения python. Некоторые, но не все, переменные в моей программе должны быть доступны интерпретатору.Как встроить интерпретатор Python в виджет PyQT

В настоящее время я использую подклассы и модифицированные 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_()) 
+0

Первый результат от Google: HTTP: //doc.trolltech.com/qq/qq23-pythonqt.html, а со второго: http://wiki.python.org/moin/EmbedingPyQtTutorial. Соответствуют ли ваши потребности? Снова: http://stackoverflow.com/questions/2742636/how-to-embed-the-python-interpreter-in-a-qt-app –

+2

Нет, мое приложение написано на Python. Эти страницы касаются упаковки приложений C (++) в python и встраивания python в приложения C (++). – Mathias

+0

Пожалуйста, забыли свой предыдущий комментарий –

ответ

12

бит поздно я знаю, но я рекомендую code.InteractiveConsole класс: http://docs.python.org/py3k/library/code.html#code.InteractiveConsole

+0

Я думаю, что это правильный ответ. InteractiveConsole позволит вам определить переменные в локальном пространстве имен, передав словарь этих переменных. i = InteractiveConsole (locals = locals()) – freakTheMighty

1

Не уверен, что вы хотите точно, но попытались сохранить содержимое виджета в временный файл и передать его в стандартный питона переводчика с Popen?

Doc здесь: 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) 
+0

Это простой, я добавил код выше, чтобы показать, что я хочу. – Mathias

2

Вы можете посмотреть в использовании потоков, чтобы сохранить пользовательский интерфейс реагировать при печати больших петель. Это также помогло бы сохранить ваши следы чистыми.

Сохранение переменных в диктофонии - это путь - это то, что сам Python делает внутренне. Что касается раскрытия «некоторых, но не всех» из них, подумайте о том, чтобы разоблачить их всех. Намного легче. Если вас беспокоит безопасность, остерегайтесь, что вы не можете надежно скрыть что-либо в Python.

Что касается отвратительного перемещения курсора/текста: воспользуйтесь тем, что у вас есть GUI. С терминалом у вас есть только одно «текстовое поле», но в Qt может быть более уместным иметь представление журнала/результата и поле ввода .

В представлении журнала отображаются введенные команды и результаты отображаются в текстовом поле только для чтения.

Текстовое поле команды позволит вам ввести команду в чистоту.

Этот подход используется в некоторых веб-фреймах - например, через WebError:

enter image description here

+0

Снимок экрана. Также рассмотрите возможность встраивания изображения непосредственно в текст ответа вместо ссылки. – Macke

+0

Спасибо, что сообщили мне! Я нашел новый скриншот, надеюсь, он также сможет получить сообщение. –

0

Похоже, что вы сделали что-то подобное моему приложению Veusz, https://veusz.github.io/. Я думал, вам будет полезно увидеть более полную реализацию. Я не могу размещать гиперссылки, но смотрю на windows/consolewindow.py для класса виджетов. Команды выполняются классом document/commandinterpreter.py. Интерфейс определен в документе/commandinterface.py.Однако в основном это делается при манипулировании диктором.

2

Первый проект обновленной версии моего кода для поддержки IPython 0,13

''' 
Created on 18-03-2012 

@author: Paweł Jarosz 
''' 
import os, sys 
import atexit 

from PySide import QtCore, QtGui 

from IPython.zmq.ipkernel import IPKernelApp 
from IPython.lib.kernel import find_connection_file, connect_qtconsole 
from IPython.frontend.qt.kernelmanager import QtKernelManager 
from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget 
from IPython.config.application import catch_config_error 

class IPythonLocalKernelApp(IPKernelApp): 
    """IPython kernel application with nonblocking loop, running in dedicated thread. 
    example: 
     app = QtGui.QApplication([]) 
     kernelapp = IPythonLocalKernelApp.instance() 
     kernelapp.start() 
     namespace = kernelapp.get_user_namespace() 
     namespace["QtGui"]=QtGui 
     namespace["QtCore"]=QtCore 
     app.exec_()""" 
    #DEFAULT_INSTANCE_ARGS starting commandline 
    DEFAULT_INSTANCE_ARGS = ['qtconsole','--pylab=inline', '--colors=linux'] 

    @catch_config_error 
    def initialize(self, argv=None): 
     super(IPythonLocalKernelApp, self).initialize(argv) 
     self.kernel.eventloop = self.loop_qt4_nonblocking 

    def loop_qt4_nonblocking(self, kernel): 
     """Non-blocking version of the ipython qt4 kernel loop""" 
     kernel.timer = QtCore.QTimer() 
     kernel.timer.timeout.connect(kernel.do_one_iteration) 
     kernel.timer.start(1000*kernel._poll_interval) 

    def start(self, argv=DEFAULT_INSTANCE_ARGS): 
     """Starts IPython kernel app 
      argv: arguments passed to kernel 
     """ 
     self.initialize(argv) 
     #self.heartbeat.start() 
     #if self.poller is not None: 
     # self.poller.start() 

     self.kernel.start() 
     super(IPythonLocalKernelApp, self).start() 


    def get_connection_file(self): 
     """Returne current kernel connection file.""" 
     return self.connection_file 

    def get_user_namespace(self): 
     """Returns current kernel userspace dict""" 
     return self.kernel.shell.user_ns 

class IPythonConsoleQtWidget(RichIPythonWidget): 
    """Ipython console Qt4+ widget 
     Usage example: 
      app = QtGui.QApplication([]) 
      kernelapp = IPythonLocalKernelApp.instance() 
      kernelapp.start() 
      namespace = kernelapp.get_user_namespace() 
      widget = IPythonConsoleQtWidget() 
      widget.set_default_style(colors='linux') 
      widget.connect_kernel(connection_file=kernelapp.get_connection_file()) 
      # if you won't to connect to remote kernel: 
      widget.connect_kernel(connection_file='kernel-16098.json') 

      widget.show() 

      namespace["widget"] = widget 
      namespace["QtGui"]=QtGui 
      namespace["QtCore"]=QtCore 

      app.exec_()""" 
    _connection_file = None 

    def __init__(self, *args, **kw): 
     RichIPythonWidget.__init__(self, *args, **kw) 
     self._existing = True 
     self._may_close = False 
     self._confirm_exit = False 

    def _init_kernel_manager(self): 
     km = QtKernelManager(connection_file=self._connection_file, config=self.config) 
     km.load_connection_file() 
     km.start_channels(hb=self._heartbeat) 
     self.kernel_manager = km 
     atexit.register(self.kernel_manager.cleanup_connection_file) 

    def connect_kernel(self, connection_file, heartbeat=False): 
     """Connect's to ipython kernel. 
     connection_file - connection file to use 
     heartbeat   - should start heartbeat server? Workaround for problems with inproc embedded kernels 
          (right click save image as/save as html kills kernel heartbeat/pool(??) serwer """ 

     self._heartbeat = heartbeat 
     if os.path.exists(connection_file): 
      self._connection_file = connection_file 
     else: 
      self._connection_file = find_connection_file(connection_file) 

     self._init_kernel_manager() 



app = QtGui.QApplication([]) 
kernelapp = IPythonLocalKernelApp.instance() 
kernelapp.start() 
namespace = kernelapp.get_user_namespace() 
widget = IPythonConsoleQtWidget() 
widget.set_default_style(colors='linux') 
widget.connect_kernel(connection_file=kernelapp.get_connection_file()) 
# if you won't to connect to remote kernel: 
# widget.connect_kernel(connection_file='kernel-16098.json') 

widget.show() 

namespace["widget"] = widget 
namespace["QtGui"]=QtGui 
namespace["QtCore"]=QtCore 

app.exec_() 
+1

BTW ... проблемы с сохранением правой кнопкой мыши по мере удаления HTML теперь;). Не стесняйтесь протестировать его. –

Смежные вопросы