Cómo incrustar un intérprete de Python en un widget PyQt
Quiero poder abrir un terminal python interactivo desde mi aplicación python. Algunas, pero no todas, las variables en mi programa necesitan ser expuestas al intérprete.
Actualmente uso un subclasificado y modificado QPlainTextEdit
y ruta todos los "comandos" allí a eval
o exec
, y mantener un registro de un espacio de nombres separado en un dict. Sin embargo, tiene que haber una forma más elegante y robusta! ¿Cómo?
Aquí hay un ejemplo haciendo justo 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í algunos. Es muy lento, pruebe un bucle de impresión grande... Tiene que ser una forma más simple y menos propensa a los errores,...¡Espero!!
Es la función def runCommand(self)
que es la clave para entender mi problema. Idealmente no quiero mejorarlo, prefiero reemplazar su contenido con algo más simple e inteligente.
El la funcionalidad de la instrucció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_())
5 answers
Un poco tarde Lo sé, pero recomiendo el código .InteractiveConsole clase: http://docs.python.org/py3k/library/code.html#code.InteractiveConsole
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-06 01:19:55
Usted podría mirar en el uso de hilos para mantener la interfaz de usuario sensible mientras se imprimen bucles grandes. Esto también ayudaría a mantener sus rastreos limpios.
Mantener las variables en un diccionario es el camino a seguir-es lo que Python hace internamente. En cuanto a exponer "algunos, pero no todos" de ellos, considere simplemente exponerlos a todos. Mucho más fácil. Si le preocupa la seguridad, tenga cuidado de que no puede ocultar nada de manera confiable en Python.
En cuanto a la horrenda manipulación del cursor/texto: aprovecha del hecho de que usted tiene una interfaz gráfica de usuario. 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 separado cuadro de comando.
La vista de registro mostrará los comandos introducidos y los resultados en un cuadro de texto de solo lectura.
El cuadro de texto comando le permitiría introducir un comando limpiamente.
Este enfoque se utiliza en algunos marcos web, por ejemplo, via WebError :
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-27 00:10:29
Primer borrador de la versión actualizada de mi código para soportar 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_()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-06 17:00:36
¿No está seguro de lo que quiere exactamente pero ha intentado guardar el contenido del widget en un archivo temporal y pasarlo a un intérprete estándar de python con Popen ?
El documento 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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-03 15:04:01
Parece que hiciste algo similar a mi aplicación Veusz, https://veusz.github.io / . Pensé que podría ser útil ver una implementación más completa. No puedo publicar hipervínculos, pero echa un vistazo a windows/consolewindow.py para la clase widget. Los comandos son ejecutados por document/commandinterpreter.py clase. La interfaz se define en document/commandinterface.py Sin embargo, la mayoría de las veces se hace manipulando un dictado.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-30 10:07:10