2010-05-03 13 views
24

Quiero ser capaz de mostrar un terminal de python interactivo desde mi aplicación de Python. Algunas, pero no todas, las variables de mi programa deben estar expuestas al intérprete.Cómo incrustar un intérprete de Python en un widget de PyQT

Actualmente utilizo un QPlainTextEdit subclasificado y modificado y envío todos los "comandos" allí a eval o exec, y sigo un espacio de nombres separado en un dict. ¡Sin embargo, tiene que haber una manera más elegante y robusta! ¿Cómo?

Aquí es un ejemplo haciendo precisamente lo que quiero, pero es con IPython y pyGTK ... http://ipython.scipy.org/moin/Cookbook/EmbeddingInGTK

A continuación se muestra lo que tengo actualmente. Pero hay tantos casos de esquina que probablemente me perdí un poco. Es muy lento, prueba un ciclo de impresión grande ... Tiene que ser una forma más simple y menos propensa a errores, ... ¡Espero!

Es la función def runCommand(self) la clave para entender mi problema. Idealmente, no quiero mejorarlo, prefiero reemplazar su contenido por algo más simple e inteligente.

La funcionalidad de la declaración console.updateNamespace({'myVar1' : app, 'myVar2' : 1234}) en "main" también es importante.

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

primer resultado de Google: http: //doc.trolltech.com/qq/qq23-pythonqt.html, y del segundo: http://wiki.python.org/moin/EmbedingPyQtTutorial. ¿Se ajustan a tus necesidades? De nuevo: http://stackoverflow.com/questions/2742636/how-to-embed-the-python-interpreter-in-a-qt-app –

+2

No, mi aplicación está escrita en Python. Esas páginas se refieren a envolver aplicaciones C (++) en python e incrustar Python en aplicaciones C (++). – Mathias

+0

Por favor, olvidé mi comentario anterior –

Respuesta

1
No

seguro de lo que quiere exactamente, pero ha tratado de guardar el contenido del widget en un un archivo temporal y pasarlo a un intérprete de Python estándar con Popen?

Doc está aquí: http://docs.python.org/release/2.6.5/library/subprocess.html#subprocess.Popen

Ejemplo:

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 básico, agregué un código arriba para mostrar lo que quiero. – Mathias

2

Usted puede mirar en el uso roscas para mantener la interfaz de usuario sensible al imprimir grandes bucles. Esto también ayudaría a mantener limpios tus tracebacks.

Mantener las variables en un dict es el camino a seguir, es lo que Python mismo hace internamente. En cuanto a exponer "algunos, pero no todos" de ellos, considere exponerlos a todos. Más fácil. Si le preocupa la seguridad, tenga cuidado de no poder ocultar nada en Python.

En cuanto a la espantosa manipulación del cursor/texto: aproveche el hecho de que tiene una GUI. Con un terminal, solo tiene un "cuadro de texto", pero en Qt, podría ser más apropiado tener una vista de registro/resultado y un cuadro de comando separado.

La vista de registro mostraría los comandos y resultados ingresados ​​en un cuadro de texto de solo lectura.

El cuadro de texto del comando le permite ingresar un comando limpiamente.

Este enfoque se utiliza en algunos marcos web, p. a través de WebError:

enter image description here

+0

Enlace de captura de pantalla roto. También considere incluir la imagen directamente en su texto de respuesta, en lugar de vincularla. – Macke

+0

¡Gracias por informarme! Encontré una nueva captura de pantalla, con suerte también puede transmitir el mensaje. –

0

Suena como usted hizo algo similar a mi solicitud Veusz, https://veusz.github.io/. Pensé que podría ser útil ver una implementación más completa. No puedo publicar hipervínculos, pero eche un vistazo a windows/consolewindow.py para la clase de widget. Los comandos se ejecutan mediante la clase document/commandinterpreter.py. La interfaz se define en document/commandinterface.py.En su mayoría, se trata de manipular un dict sin embargo.

2

Primer borrador de la versión actualizada de mi código para apoyar 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 ... problemas con el botón derecho del mouse guardar como HTML ahora se han ido;). Siéntase libre de probarlo. –

Cuestiones relacionadas