ipython / ipykernel

IPython Kernel for Jupyter
https://ipykernel.readthedocs.io/en/stable/
BSD 3-Clause "New" or "Revised" License
645 stars 365 forks source link

AttributeError: 'InProcessKernel' object has no attribute 'io_loop' #319

Open ftpronk opened 6 years ago

ftpronk commented 6 years ago

I filed a similar bug under the jupyter/qtconsole project, but later narrowed the problem down to ipykernel, so I'm opening a new bug report here.

I was working with an IPython qtconsole embedded in some C++ code, which used to work. But after a recent update, I cannot exit the console/quit my app using exit/exit()/quit/quit().

The problem seems to have been introduced in ipykernel==4.7, downgrading to ipykernel==4.6.1 solves the problem. Here's a minimal test example:

import os
os.environ['QT_API'] = 'pyside'
from qtconsole.qt import QtGui
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtconsole.inprocess import QtInProcessKernelManager
from IPython.lib import guisupport

class QIPythonWidget(RichJupyterWidget):

  def __init__(self, *args, **kwargs):
    super(RichJupyterWidget, self).__init__(*args, **kwargs)
    self.kernel_manager = QtInProcessKernelManager()
    self.kernel_manager.start_kernel()
    self.kernel = self.kernel_manager.kernel
    self.kernel.gui = 'qt4'
    self.kernel.shell.push(kwargs)
    self.kernel_client = self.kernel_manager.client()
    self.kernel_client.start_channels()

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

if __name__ == "__main__":
  app = QtGui.QApplication([])
  wid = QIPythonWidget()
  wid.show()
  app.exec_()

The error returned is the following:

In [1]: exit
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-1-e828459ebe2b> in <module>()
----> 1 exit()

/usr/local/lib/python2.7/dist-packages/IPython/core/autocall.pyc in __call__(self, keep_kernel)
     68     def __call__(self, keep_kernel=False):
     69         self._ip.keepkernel_on_exit = keep_kernel
---> 70         self._ip.ask_exit()

/usr/local/lib/python2.7/dist-packages/ipykernel/zmqshell.pyc in ask_exit(self)
    526     def ask_exit(self):
    527         """Engage the exit actions."""
--> 528         self.exit_now = (not self.keepkernel_on_exit)
    529         payload = dict(
    530             source='ask_exit',

/usr/local/lib/python2.7/dist-packages/traitlets/traitlets.pyc in __set__(self, obj, value)
    583             raise TraitError('The "%s" trait is read-only.' % self.name)
    584         else:
--> 585             self.set(obj, value)
    586 
    587     def _validate(self, obj, value):

/usr/local/lib/python2.7/dist-packages/traitlets/traitlets.pyc in set(self, obj, value)
    572             # we explicitly compare silent to True just in case the equality
    573             # comparison above returns something other than True/False
--> 574             obj._notify_trait(self.name, old_value, new_value)
    575 
    576     def __set__(self, obj, value):

/usr/local/lib/python2.7/dist-packages/traitlets/traitlets.pyc in _notify_trait(self, name, old_value, new_value)
   1137             new=new_value,
   1138             owner=self,
-> 1139             type='change',
   1140         ))
   1141 

/usr/local/lib/python2.7/dist-packages/traitlets/traitlets.pyc in notify_change(self, change)
   1174                 c = getattr(self, c.name)
   1175 
-> 1176             c(change)
   1177 
   1178     def _add_notifiers(self, handler, name, type):

/usr/local/lib/python2.7/dist-packages/ipykernel/zmqshell.pyc in _update_exit_now(self, change)
    470         """stop eventloop when exit_now fires"""
    471         if change['new']:
--> 472             loop = self.kernel.io_loop
    473             loop.call_later(0.1, loop.stop)
    474             if self.kernel.eventloop:

AttributeError: 'InProcessKernel' object has no attribute 'io_loop'

In [2]: 

The output of python -c "import IPython; print(IPython.sys_info())" on my machine is:

{'commit_hash': u'b467d487e',
 'commit_source': 'installation',
 'default_encoding': 'UTF-8',
 'ipython_path': '/usr/local/lib/python2.7/dist-packages/IPython',
 'ipython_version': '5.5.0',
 'os_name': 'posix',
 'platform': 'Linux-4.10.0-42-generic-x86_64-with-LinuxMint-18.3-sylvia',
 'sys_executable': '/usr/bin/python',
 'sys_platform': 'linux2',
 'sys_version': '2.7.12 (default, Dec  4 2017, 14:50:18) \n[GCC 5.4.0 20160609]'}

The pip versions of the (relevant) packages on my system are:

ipykernel==4.8.2
ipython==5.5.0
ipython-genutils==0.2.0
ipywidgets==7.1.2
...
jupyter==1.0.0
jupyter-client==5.2.2
jupyter-console==5.2.0
jupyter-core==4.4.0
...
pyzmq==17.0.0
qtconsole==4.3.1
tornado==5.0
traitlets==4.3.2

And I'm using the system package for PySide, version 1.2.2

polwel commented 5 years ago

I ran into the same issue. It means that any exception that occurs from code that the user typed in crashes the kernel.

I know that InProcessKernel is pretty much deprecated, but as of now, there is no (well documented) alternative. Is this something that might get fixed by the devs, or is this the final death sentence for InProcessKernel?

sdh4 commented 4 years ago

The problem seems to be that the io_loop attribute (along with the msg_queue attribute) are normally set by the kernel base class in ipykernel.kernelbase.Kernel.start(). Normally the kernel base class start method would be called when the kernel was started, but the InProcessKernel start method of ipykernel.inprocess.ipkernel.InProcessKernel seems to not call the baseclass start() method in order to "override registration of dispatchers for streams".

In my application, kernel.control_stream is None and there are no entries in kernel.shell_streams so the stream dispatchers wouldn't be registered anyway.

My workaround is to call the base class method directly on condition that the io_loop attribute is missing after starting the in process kernel:

kernel.start()
if not hasattr(kernel,"io_loop"):
    import ipykernel.kernelbase
    ipykernel.kernelbase.Kernel.start(kernel)

Suggested permanent fix: Either add the io_loop and msg_queue initialization to the InProcessKernel.start() method or have that method call the superclass if the need to not register dispatchers is obsolete