我想将IPython qt控制台窗口小部件嵌入正在处理的PyQt应用程序中。下面提供的代码(并改编自(http://codingdict.com/questions/162023))针对IPython v0.12实现了此功能。然而,这在崩溃的IPython V0.13在该行self.heartbeat.start()有RuntimeError: threads can only be started once。注释掉此行将调出小部件,但不响应用户输入。
self.heartbeat.start()
RuntimeError: threads can only be started once
有谁知道如何实现IPython v0.13的等效功能?
""" Adapted from https://stackoverflow.com/a/9796491/1332492 """ import os import atexit from IPython.zmq.ipkernel import IPKernelApp from IPython.lib.kernel import find_connection_file 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 from PyQt4 import QtCore class IPythonLocalKernelApp(IPKernelApp): DEFAULT_INSTANCE_ARGS = [''] @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() class IPythonConsoleQtWidget(RichIPythonWidget): _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): 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() def main(**kwargs): kernelapp = IPythonLocalKernelApp.instance() kernelapp.start() widget = IPythonConsoleQtWidget() widget.connect_kernel(connection_file=kernelapp.connection_file) widget.show() return widget if __name__ == "__main__": from PyQt4.QtGui import QApplication app = QApplication(['']) main() app.exec_()
v0.13的回溯
RuntimeError Traceback (most recent call last) /Users/beaumont/terminal.py in <module>() 80 from PyQt4.QtGui import QApplication 81 app = QApplication(['']) ---> 82 main() global main = <function main at 0x106d0c848> 83 app.exec_() /Users/beaumont/terminal.py in main(**kwargs={}) 69 def main(**kwargs): 70 kernelapp = IPythonLocalKernelApp.instance() ---> 71 kernelapp.start() kernelapp.start = <bound method IPythonLocalKernelApp.start of <__main__.IPythonLocalKernelApp object at 0x106d10590>> 72 73 widget = IPythonConsoleQtWidget() /Users/beaumont/terminal.py in start(self=<__main__.IPythonLocalKernelApp object>, argv=['']) 33 """ 34 self.initialize(argv) ---> 35 self.heartbeat.start() self.heartbeat.start = <bound method Heartbeat.start of <Heartbeat(Thread-1, started daemon 4458577920)>> 36 37 if self.poller is not None: /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc in start(self=<Heartbeat(Thread-1, started daemon 4458577920)>) 487 raise RuntimeError("thread.__init__() not called") 488 if self.__started.is_set(): --> 489 raise RuntimeError("threads can only be started once") global RuntimeError = undefined 490 if __debug__: 491 self._note("%s.start(): starting thread", self) RuntimeError: threads can only be started once
好的,这段代码似乎可以解决问题(即,它在Qt小部件中放置了非阻塞的ipython解释器,可以将其嵌入到其他小部件中)。传递来terminal_widget添加到小部件名称空间的关键字
terminal_widget
import atexit from IPython.zmq.ipkernel import IPKernelApp from IPython.lib.kernel import find_connection_file from IPython.frontend.qt.kernelmanager import QtKernelManager from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget from IPython.utils.traitlets import TraitError from PyQt4 import QtGui, QtCore def event_loop(kernel): kernel.timer = QtCore.QTimer() kernel.timer.timeout.connect(kernel.do_one_iteration) kernel.timer.start(1000*kernel._poll_interval) def default_kernel_app(): app = IPKernelApp.instance() app.initialize(['python', '--pylab=qt']) app.kernel.eventloop = event_loop return app def default_manager(kernel): connection_file = find_connection_file(kernel.connection_file) manager = QtKernelManager(connection_file=connection_file) manager.load_connection_file() manager.start_channels() atexit.register(manager.cleanup_connection_file) return manager def console_widget(manager): try: # Ipython v0.13 widget = RichIPythonWidget(gui_completion='droplist') except TraitError: # IPython v0.12 widget = RichIPythonWidget(gui_completion=True) widget.kernel_manager = manager return widget def terminal_widget(**kwargs): kernel_app = default_kernel_app() manager = default_manager(kernel_app) widget = console_widget(manager) #update namespace kernel_app.shell.user_ns.update(kwargs) kernel_app.start() return widget app = QtGui.QApplication([]) widget = terminal_widget(testing=123) widget.show() app.exec_()