2015-04-02 117 views
4

我试图将iPython Qtconsole嵌入到PyQt5应用程序中。嵌入式控制台工作正常,但当我尝试退出应用程序时(通过单击“退出”,使用Cmd-Q),Python进程挂起,我必须强制退出才能解散死亡的旋转沙滩球。这在OS X 10.10.2,Python 2.7.9,iPython 3.0.0和PyQt5 5.3.1上。有关如何正确退出的想法?使用嵌入式iPython无法退出PyQt5应用程序Qtconsole

小例子,改编自iPython examples

#!/usr/bin/env python 
from PyQt5 import Qt 

from internal_ipkernel import InternalIPKernel 

class SimpleWindow(Qt.QWidget, InternalIPKernel): 

    def __init__(self, app): 
     Qt.QWidget.__init__(self) 
     self.app = app 
     self.add_widgets() 
     self.init_ipkernel('qt') 

    def add_widgets(self): 
     self.setGeometry(300, 300, 400, 70) 
     self.setWindowTitle('IPython in your app') 

     # Add simple buttons: 
     self.console = Qt.QPushButton('Qt Console', self) 
     self.console.setGeometry(10, 10, 100, 35) 
     self.console.clicked.connect(self.new_qt_console) 

     self.namespace = Qt.QPushButton('Namespace', self) 
     self.namespace.setGeometry(120, 10, 100, 35) 
     self.namespace.clicked.connect(self.print_namespace) 

     self.count_button = Qt.QPushButton('Count++', self) 
     self.count_button.setGeometry(230, 10, 80, 35) 
     self.count_button.clicked.connect(self.count) 

     # Quit and cleanup 
     self.quit_button = Qt.QPushButton('Quit', self) 
     self.quit_button.setGeometry(320, 10, 60, 35) 
     self.quit_button.clicked.connect(self.app.quit) 

     self.app.lastWindowClosed.connect(self.app.quit) 

     self.app.aboutToQuit.connect(self.cleanup_consoles) 

if __name__ == "__main__": 
    app = Qt.QApplication([]) 
    # Create our window 
    win = SimpleWindow(app) 
    win.show() 

    # Very important, IPython-specific step: this gets GUI event loop 
    # integration going, and it replaces calling app.exec_() 
    win.ipkernel.start() 

internal_ipkernel.py

import sys 

from IPython.lib.kernel import connect_qtconsole 
from IPython.kernel.zmq.kernelapp import IPKernelApp 

def mpl_kernel(gui): 
    """Launch and return an IPython kernel with matplotlib support for the desired gui 
    """ 
    kernel = IPKernelApp.instance() 
    kernel.initialize(['python', '--matplotlib=%s' % gui, 
         #'--log-level=10' 
         ]) 
    return kernel 


class InternalIPKernel(object): 

    def init_ipkernel(self, backend): 
     # Start IPython kernel with GUI event loop and mpl support 
     self.ipkernel = mpl_kernel(backend) 
     # To create and track active qt consoles 
     self.consoles = [] 

     # This application will also act on the shell user namespace 
     self.namespace = self.ipkernel.shell.user_ns 

     # Example: a variable that will be seen by the user in the shell, and 
     # that the GUI modifies (the 'Counter++' button increments it): 
     self.namespace['app_counter'] = 0 
     #self.namespace['ipkernel'] = self.ipkernel # dbg 

    def print_namespace(self, evt=None): 
     print("\n***Variables in User namespace***") 
     for k, v in self.namespace.items(): 
      if not k.startswith('_'): 
       print('%s -> %r' % (k, v)) 
     sys.stdout.flush() 

    def new_qt_console(self, evt=None): 
     """start a new qtconsole connected to our kernel""" 
     return connect_qtconsole(self.ipkernel.connection_file, profile=self.ipkernel.profile) 

    def count(self, evt=None): 
     self.namespace['app_counter'] += 1 

    def cleanup_consoles(self, evt=None): 
     for c in self.consoles: 
      c.kill() 

回答

5

你必须使用一个内核管理者,就像这样: Embedding IPython Qt console in a PyQt application

这里是一个工作示例对于PyQt5:

import os 
os.environ['QT_API'] = 'pyqt5' 

from PyQt5 import QtWidgets 
from PyQt5 import QtGui 
# ipython won't work if this is not correctly installed. And the error message will be misleading 
from PyQt5 import QtSvg 

# Import the console machinery from ipython 
from IPython.qt.console.rich_ipython_widget import RichIPythonWidget 
from IPython.qt.inprocess import QtInProcessKernelManager 


def get_app_qt5(*args, **kwargs): 
    """Create a new qt5 app or return an existing one.""" 
    app = QtWidgets.QApplication.instance() 
    if app is None: 
     if not args: 
      args = ([''],) 
     app = QtWidgets.QApplication(*args, **kwargs) 
    return app 

class QIPythonWidget(RichIPythonWidget): 
    """ Convenience class for a live IPython console widget. We can replace the standard banner using the customBanner argument""" 
    def __init__(self,customBanner=None,*args,**kwargs): 
     super(QIPythonWidget, self).__init__(*args,**kwargs) 
     if customBanner!=None: self.banner=customBanner 
     self.kernel_manager = kernel_manager = QtInProcessKernelManager() 
     kernel_manager.start_kernel() 
     kernel_manager.kernel.gui = 'qt' 
     self.kernel_client = kernel_client = self._kernel_manager.client() 
     kernel_client.start_channels() 

     def stop(): 
      kernel_client.stop_channels() 
      kernel_manager.shutdown_kernel() 
      get_app_qt5().exit()    
     self.exit_requested.connect(stop) 

    def pushVariables(self,variableDict): 
     """ Given a dictionary containing name/value pairs, push those variables to the IPython console widget """ 
     self.kernel_manager.kernel.shell.push(variableDict) 
    def clearTerminal(self): 
     """ Clears the terminal """ 
     self._control.clear()  
    def printText(self,text): 
     """ Prints some plain text to the console """ 
     self._append_plain_text(text)   
    def executeCommand(self,command): 
     """ Execute a command in the frame of the console widget """ 
     self._execute(command,False) 


class ExampleWidget(QtWidgets.QMainWindow): 
    """ Main GUI Window including a button and IPython Console widget inside vertical layout """ 
    def __init__(self, parent=None): 
     super(ExampleWidget, self).__init__(parent) 
     self.setWindowTitle('iPython in PyQt5 app example') 
     self.mainWidget = QtWidgets.QWidget(self) 
     self.setCentralWidget(self.mainWidget) 
     layout = QtWidgets.QVBoxLayout(self.mainWidget) 
     self.button = QtWidgets.QPushButton('Another widget') 
     ipyConsole = QIPythonWidget(customBanner="Welcome to the embedded ipython console\n") 
     layout.addWidget(self.button) 
     layout.addWidget(ipyConsole)   
     # This allows the variable foo and method print_process_id to be accessed from the ipython console 
     ipyConsole.pushVariables({"foo":43,"print_process_id":print_process_id}) 
     ipyConsole.printText("The variable 'foo' and the method 'print_process_id()' are available. Use the 'whos' command for information.\n\nTo push variables run this before starting the UI:\n ipyConsole.pushVariables({\"foo\":43,\"print_process_id\":print_process_id})") 
     self.setGeometry(300, 300, 800, 600) 

def print_process_id(): 
    print('Process ID is:', os.getpid()) 

def main(): 
    app = get_app_qt5() 
    widget = ExampleWidget() 
    widget.show() 
    app.exec_() 

if __name__ == '__main__': 
    main() 
+0

天才,谢谢! – Winawer 2015-05-13 23:17:02

+0

有没有什么办法可以将应用程序的stdout/stderr/stdin重定向到嵌入式的qt-console! – Suresh 2017-10-16 07:09:25

+0

@Suresh你应该创建一个新的问题 – 2017-10-17 12:27:38