2010-05-03 12 views
24

Ich möchte ein interaktives Python-Terminal aus meiner Python-Anwendung aufrufen können. Einige, aber nicht alle Variablen in meinem Programm müssen dem Interpreter zur Verfügung gestellt werden.Einbetten eines Python-Interpreters in ein PyQT-Widget

Zur Zeit verwende ich eine untergeordnete und geänderte QPlainTextEdit und route alle "Befehle" dort zu eval oder exec, und verfolgen Sie einen separaten Namespace in einem dict. Aber es muss einen eleganteren und robusteren Weg geben! Wie?

Hier ist ein Beispiel nur das tun, was ich will, aber es ist mit IPython und pyGTK ... http://ipython.scipy.org/moin/Cookbook/EmbeddingInGTK

Im Folgenden finden Sie, was ich habe zur Zeit. Aber es gibt so viele Eckfälle, dass ich wahrscheinlich einige vermisst habe. Es ist sehr langsam, versuche eine große Druckschleife ... Es muss einfacher und weniger fehleranfällig sein, ... Ich hoffe !!

Es ist die def runCommand(self) Funktion, die der Schlüssel zum Verständnis meines Problems ist. Ich möchte es im Idealfall nicht verbessern, sondern den Inhalt durch etwas einfacheres und intelligenteres ersetzen.

Die Funktionalität der console.updateNamespace({'myVar1' : app, 'myVar2' : 1234}) Anweisung in "Haupt" ist auch wichtig.

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

Erstes Ergebnis von Google zu unterstützen: http: //doc.trolltech.com/qq/qq23-pythonqt.html und von der zweiten Seite: http://wiki.python.org/moin/EmpedingPyQtTutorial. Passen diese zu Ihren Bedürfnissen? Nochmal: http://stackoverflow.com/questions/2742636/how-to-embed-the-python-interpreter-in-a-qt-app –

+2

Nein, meine Anwendung ist in Python geschrieben. Diese Seiten betreffen die Umhüllung von C (++) Anwendungen in Python und die Einbettung von Python in C (++) Anwendungen. – Mathias

+0

Bitte vergessen Sie meinen vorherigen Kommentar –

Antwort

1

Nicht sicher, was Sie genau wollen, aber versucht haben, den Inhalt des Widgets in eine temporäre Datei zu speichern und mit Popen an einen Standard-Python-Interpreter zu übergeben?

Doc ist hier: http://docs.python.org/release/2.6.5/library/subprocess.html#subprocess.Popen

Beispiel:

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

Es ist zu grundlegend, ich habe etwas Code oben hinzugefügt, um zu zeigen, was ich will. – Mathias

2

Sie in Verwendung von Threads aussehen könnte die Benutzeroberfläche reagiert, zu halten, während große Schleifen zu drucken. Dies würde auch helfen, Ihre Rückverfolgungen sauber zu halten.

Variablen in einem Diktat zu halten, ist der richtige Weg - das macht Python selbst intern. Wenn Sie "einige, aber nicht alle" von ihnen aufdecken, sollten Sie sie alle bloßstellen. Viel einfacher. Wenn Sie sich Sorgen um die Sicherheit machen, sollten Sie darauf achten, dass Sie in Python nichts verlässlich verbergen können.

Wie für die scheußliche Cursor/Text-Manipulation: Nutzen Sie die Tatsache, dass Sie eine GUI haben. Mit einem Terminal haben Sie nur ein "Textfeld", aber in Qt, könnte es besser sein, eine Protokoll-/Ergebnisansicht und eine separate Kommandozeile zu haben.

Die Protokollansicht zeigt die eingegebenen Befehle an und führt zu einer schreibgeschützten Textbox.

Das Befehlstextfeld ermöglicht es Ihnen, einen Befehl sauber einzugeben.

Dieser Ansatz wird in einigen Web-Frameworks verwendet - z. über WebError:

enter image description here

+0

Screenshot Verbindung unterbrochen. Erwägen Sie auch, das Bild direkt in Ihren Antworttext einzubetten, anstatt es zu verknüpfen. – Macke

+0

Danke, dass Sie mich wissen lassen! Ich habe einen neuen Screenshot gefunden, hoffentlich kann er die Nachricht auch vermitteln. –

0

Es klingt wie Sie etwas ähnliches wie meine Veusz Anwendung tat, https://veusz.github.io/. Ich dachte, Sie könnten es nützlich finden, eine vollständigere Implementierung zu sehen. Ich kann keine Hyperlinks veröffentlichen, aber werfen Sie einen Blick auf windows/consolewindow.py für die Widget-Klasse. Befehle werden von der Klasse document/commandinterpreter.py ausgeführt. Die Schnittstelle ist in document/commandinterface.py definiert.Meist ist es aber ein Diktat zu manipulieren.

2

Ersten Entwurf der aktualisierten Version von meinem Code 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 ... Probleme mit der rechten Maustaste speichern als HTML sind jetzt weg;). Fühlen Sie sich frei, es zu testen. –

Verwandte Themen