Repository: ipython/ipython

Issue:
PyQt5 qtconsole gives TypeError: __init__() takes exactly 4 arguments (1 given)
As reported by @jenshnielsen on the merged pull-request here: https://github.com/ipython/ipython/pull/5458 qtconsole gives an error on PyQt5.

```
QT_API=pyqt5 ipython qtconsole
Traceback (most recent call last):
  File "/Users/jhn/Envs/ipython_master/bin/ipython", line 9, in <module>
    load_entry_point('ipython==3.0.0-dev', 'console_scripts', 'ipython')()
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/__init__.py", line 120, in start_ipython
    return launch_new_instance(argv=argv, **kwargs)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 548, in launch_instance
    app.initialize(argv)
  File "<string>", line 2, in initialize
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 74, in catch_config_error
    return method(app, *args, **kwargs)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/terminal/ipapp.py", line 319, in initialize
    super(TerminalIPythonApp, self).initialize(argv)
  File "<string>", line 2, in initialize
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 74, in catch_config_error
    return method(app, *args, **kwargs)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/core/application.py", line 378, in initialize
    self.parse_command_line(argv)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/terminal/ipapp.py", line 314, in parse_command_line
    return super(TerminalIPythonApp, self).parse_command_line(argv)
  File "<string>", line 2, in parse_command_line
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 74, in catch_config_error
    return method(app, *args, **kwargs)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 460, in parse_command_line
    return self.initialize_subcommand(subc, subargv)
  File "<string>", line 2, in initialize_subcommand
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 74, in catch_config_error
    return method(app, *args, **kwargs)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 398, in initialize_subcommand
    self.subapp.initialize(argv)
  File "<string>", line 2, in initialize
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/config/application.py", line 74, in catch_config_error
    return method(app, *args, **kwargs)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/qt/console/qtconsoleapp.py", line 344, in initialize
    IPythonConsoleApp.initialize(self,argv)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/consoleapp.py", line 352, in initialize
    self.init_kernel_client()
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/consoleapp.py", line 339, in init_kernel_client
    self.kernel_client.start_channels()
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/qt/kernel_mixins.py", line 208, in start_channels
    super(QtKernelClientMixin, self).start_channels(*args, **kw)
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/kernel/client.py", line 109, in start_channels
    self.shell_channel.start()
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/kernel/client.py", line 153, in shell_channel
    self.context, self.session, url
  File "/Users/jhn/Envs/ipython_master/lib/python2.7/site-packages/IPython/qt/util.py", line 67, in __new__
    QtCore.QObject.__init__(inst)
TypeError: __init__() takes exactly 4 arguments (1 given)
```

Having attempted to look into this, it appears the `TypeError` is triggered only by the Qt channel objects:

```
IPython.qt.client.QtIOPubChannel
IPython.qt.client.QtShellChannel
IPython.qt.client.QtStdInChannel
IPython.qt.client.QtHBChannel
```

The behaviour of SuperQObject is a bit beyond me at present. However, wrapping the init that throws the TypeError in a try/except clause (i.e. allowing it to fail) results in it working fine.



Discussion:
As I wrote in #5458, PyQt5 now automatically calls the default init() methods (with no arguments) of all Mixin classes. Some classes are currently not default constructable. I added default inits for the classes required to launch the embedded console, but apparently there are others that need to be adapted for running qtconsole.

So at the moment, the PyQt5.QtCore.QObject constructor first calls **init** of Mixin classes with no arguments (which fails if there are additional arguments without default values). Then the SuperQObject continues by calling the **init** methods again and provides the required arguments for proper initialization.

Thanks for the clarification @pwuertz I hadn't understood what you meant the first time. I've added default values to the channel **init** e.g. in IPython/kernel/channels.py

```
class HBChannel(ZMQSocketChannel):
    def __init__(self, context=None, session=None, address=None):
```

...(repeated for each channel constructor) and this resolves the bug. Is this what you had in mind? I'm happy to do the pull-request if so. Thanks!

Yes exactly. We (or the IPython core developers) have to keep in mind though that for PyQt5 the init methods are called two times. This isn't a problem if init just initializes object members.

Wait, `__init__` is called twice? I'd rather avoid that if it's practical - even if it works now, it could cause very hard to understand bugs later if someone adds code that doesn't allow that.

Yes, because QObject in PyQt5 basically includes the functionality of SuperQObject from IPython. At the time it wasn't clear to me how SuperQObject worked, neither the implicit init calls that originated from PyQt5-QObject. I guess in order to avoid double inits one would have to exclude the init calls within SuperQObject based on the loaded QtApi, and if so, provide **kwargs to QObject.**init** directly since it now handles additional args..

OK, I'm inclined to say that we should actually do that - have a different code path for PyQt5 from the other PyQt APIs. It might be a bit ugly, but I think it's better to record that difference in the code now than try to rediscover it when something breaks further down the line. Do you have time to make a PR?

I gave that idea a try but I'm getting really confusing errors. Since I never worked with Python Meta-Classes, the complexity of the Meta-Class, Mixin, Multi-Inheritance structure puzzles me. In order to get to the bottom of this I'm letting SuperQObject print any `__new__` and `__init__` calls. This is what happens:

```
New <class 'IPython.qt.manager.QtKernelManager'>
New <class 'IPython.qt.manager.QtKernelRestarter'>
New <class 'IPython.qt.client.QtKernelClient'>
New <class 'IPython.qt.client.QtShellChannel'>
Init <class 'IPython.qt.client.QtShellChannel'>
New <class 'IPython.qt.client.QtIOPubChannel'>
Init <class 'IPython.qt.client.QtIOPubChannel'>
New <class 'IPython.qt.client.QtStdInChannel'>
Init <class 'IPython.qt.client.QtStdInChannel'>
New <class 'IPython.qt.client.QtHBChannel'>
Init <class 'IPython.qt.client.QtHBChannel'>
```

Then the application crashes because the QtKernelClient object is used before calling its `__init__` method. As far as I can see the objects that didn't initialize properly are based on the MetaQObjectHasTraits structure.
Switching the order of SuperQObject and HasTraits in MetaQObjectHasTraits seems to get me one step further, but then the Trait-framework raises critical errors.


Resolving patch:
diff --git a/IPython/kernel/channels.py b/IPython/kernel/channels.py
--- a/IPython/kernel/channels.py
+++ b/IPython/kernel/channels.py
@@ -47,7 +47,7 @@ class HBChannel(Thread):
     _pause = None
     _beating = None
 
-    def __init__(self, context, session, address):
+    def __init__(self, context=None, session=None, address=None):
         """Create the heartbeat monitor thread.
 
         Parameters
diff --git a/IPython/qt/console/ipython_widget.py b/IPython/qt/console/ipython_widget.py
--- a/IPython/qt/console/ipython_widget.py
+++ b/IPython/qt/console/ipython_widget.py
@@ -280,7 +280,7 @@ def _handle_kernel_info_reply(self, rep):
         """Handle kernel info replies."""
         content = rep['content']
         if not self._guiref_loaded:
-            if content.get('language') == 'python':
+            if content.get('implementation') == 'ipython':
                 self._load_guiref_magic()
             self._guiref_loaded = True
         
diff --git a/IPython/qt/console/magic_helper.py b/IPython/qt/console/magic_helper.py
--- a/IPython/qt/console/magic_helper.py
+++ b/IPython/qt/console/magic_helper.py
@@ -95,7 +95,7 @@ def sizeHint(self):
         self.search_line.textChanged[str].connect(
             self.search_changed
         )
-        self.search_list.itemDoubleClicked[QtGui.QListWidgetItem].connect(
+        self.search_list.itemDoubleClicked.connect(
             self.paste_requested
         )
         self.paste_button.clicked[bool].connect(


Test patch: