5

I am trying to get around this error:

QtWebEngineWidgets must be imported before a QCoreApplication instance is created.

It is pretty self-explanatory, but I am trying to make a GUI to be used within iPython which may be imported after a QApplication instance has been created.

How can I get around this error and create a PyQt5 GUI that can show HTML pages and is able to be imported even after the user has used a QApplication instance (such as through matplotlib)?

I tried this but still get the same error:

from PyQt5 import QtWidgets
import seaborn as sns
sns.boxplot([1],[1])
QtWidgets.QApplication.instance().quit()
from PyQt5 import QtWidgets, QtWebEngineWidgets
pyjamas
  • 4,608
  • 5
  • 38
  • 70
  • try: remove `import PyQt5.QtWebEngineWidgets` and change to `from PyQt5 import QtWidgets, QtWebEngineWidgets` – eyllanesc Aug 09 '19 at 08:48
  • @eyllanesc It gives the same error – pyjamas Aug 09 '19 at 09:06
  • Do you only have that error in ipython? – eyllanesc Aug 09 '19 at 09:07
  • @eyllanesc No it happens even if I run the code in the OP directly as a script. It's just that scripts generally have all imports at the top of the file which would avoid this problem. Which is why I mention that I am trying to make a GUI to be used in iPython that people will import after potentially having already created a QApplication – pyjamas Aug 09 '19 at 09:12
  • 1
    I mean my modifications work correctly except in ipython. Am I correct? – eyllanesc Aug 09 '19 at 09:16
  • @eyllanesc No I get the error even when running the code normally as a script. Importing QtWebEngineWidgets after plotting with Seaborn (or doing anything else involving a PyQt5 backend) gives the same ImportError even with `from PyQt5 import QtWidgets, QtWebEngineWidgets` – pyjamas Aug 09 '19 at 09:23
  • 1
    Typically, imports are at the beginning of the script. Why should you import it at the end? Why do you run `QtWidgets.QApplication.instance().quit()`? – eyllanesc Aug 09 '19 at 09:26
  • @eyllanesc Well this GUI is for data analysis, and often when doing data analysis in iPython or Jupyter people will have a bunch of results in memory and then decide to visualize the data using my GUI without having planned it beforehand. So the problem is where someone decides to import this during an ad hoc analysis that is already in progress, and I'm just wondering if there's a way around restarting the whole Python kernel and importing QtWebEngineWidgets then redoing everything in that scenario – pyjamas Aug 09 '19 at 09:32
  • The reason I ran `QtWidgets.QApplication.instance().quit()` was in an attempt to destroy the QCoreApplication instance and then import QtWebEngineWidgets, since the error message says QtWebEngineWidgets needs to be imported before a QCoreApplication instance is created... but it didn't work. – pyjamas Aug 09 '19 at 09:36
  • 1
    @Esostack There's no way to fix this in code. Internally, QtWebEngineWidgets uses [Q_COREAPP_STARTUP_FUNCTION](https://doc.qt.io/qt-5/qcoreapplication.html#Q_COREAPP_STARTUP_FUNCTION), so you really ***must*** do things in the right order (hence the error). The only work-around would be for users to somehow configure ipython to pre-load the QtWebEngineWidgets module (which is probably unacceptable). Do you *really* need to use web-engine for displaying your html? Why not use [QTextBrowser](https://doc.qt.io/qt-5/qtextbrowser.html)? – ekhumoro Aug 09 '19 at 12:07
  • @ekhumoro The HTML file includes JavaScript, so QTextBrowser doesn't work. It's an HTML file generated by Plot.ly to show graphs. This is the widget in question https://www.screencast.com/t/O4nslSLN1luA – pyjamas Aug 09 '19 at 14:50
  • 1
    @Esostack It is possible to hack around this issue by using [sip.delete](https://www.riverbankcomputing.com/static/Docs/sip/python_api.html#sip.delete) to remove the underlying C++ instance of the `QApplication`. However, there is a nasty potential problem with this approach: if a third-party module is holding a python reference to the application, and then tries to access it later on, a runtime-error will be raised. Since there is no way to eliminate this possibility, I can't really recommend the approach. – ekhumoro Aug 09 '19 at 18:51
  • @ekhumoro Very nice, thanks! That's what I was trying to do initially with `quit()`. If you post this as an answer I will accept yours. I see what you mean about potentially nasty side effects but (with Seaborn and Matplotlib, at least) if I create a new QApplication instance after deleting it and importing QtWebEngineWidgets I don't get the crash... see the code I posted below. – pyjamas Aug 09 '19 at 19:13

1 Answers1

4

There is a hack for working around this problem, but it may not be totally robust.

The idea is to delete all C++ references to the QApplication (if it exists), so that the web-engine module can be imported safely. This can be done using sip, but there are some caveats: if a third-party module has saved a python reference to the old QApplication and then tries to use it, it will raise an error like this:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: wrapped C/C++ object of type QApplication has been deleted

In practice, it's hard to predict how likely this is to happen, since you cannot legislate for how other python libraries are written. The webengine_hack function in the script below tries to do as much as possible to reduce the likelihood of the problems mentioned above:

def webengine_hack():
    from PyQt5 import QtWidgets
    app = QtWidgets.QApplication.instance()
    if app is not None:
        import sip
        app.quit()
        sip.delete(app)
    import sys
    from PyQt5 import QtCore, QtWebEngineWidgets
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
    app = QtWidgets.qApp = QtWidgets.QApplication(sys.argv)
    return app

try:
    # just for testing
    from PyQt5 import QtWidgets
    app = QtWidgets.QApplication([''])
    from PyQt5 import QtWebEngineWidgets
except ImportError as exception:
    print('\nRetrying webengine import...')
    app = webengine_hack()
    from PyQt5 import QtWebEngineWidgets

view = QtWebEngineWidgets.QWebEngineView()
view.setHtml('<h1>Hello World</h1>')
view.show()

app.exec()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • 1
    I've been using this hack and sometimes running into `RuntimeError: wrapped C/C++ object of type QApplication has been deleted`, but I was able to fix that error and still solve the initial problem by replacing `QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)` and `app = QtWidgets.qApp = QtWidgets.QApplication(sys.argv)` with `app.__init__(sys.argv)`. I don't know if that will cause even worse side-effects somehow, but @ekhumoro if there's nothing wrong with that you could update your answer for future readers – pyjamas Jul 21 '20 at 15:11