0

I am trying to write something similar to a PyPlot windowed renderer using PyQt5. The core idea is that this is not supposed to be a PyQt application that constantly runs, but instead runs only on demand to visualize computational results.

A related question suggests that the correct approach is to use only one QApplication instance; however, this will not work for my use-case (a GUI interface is optional, it is secondary to main functionality, and will be needed infrequently).

The main code is supposed to do some data crunching, launch this PyQt app, plot some figures, kill the window, do more data crunching, launch the PyQt app a second time, plot those new results, etc. etc. etc.

However, on the second launch of this app, it will always crash with a seg fault.

[1]    54413 segmentation fault  python my-fake-script-name.py

What is going on here?

import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWebEngineWidgets import QWebEngineProfile


class MainWindowWeb(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tw_title = "TINerator"
        self.browser = QWebEngineView()
        self.browser.loadFinished.connect(self.onLoadFinished)
        self.setCentralWidget(self.browser)
        self.setWindowTitle(f"{self.tw_title} (Loading...)")

    def setParams(
        self, title: str = None, window_size: tuple = None, allow_resize: bool = False
    ):
        if title:
            self.tw_title = title
            self.setWindowTitle(f"{title} (Loading...)")

        if window_size:
            self.resize(window_size[0], window_size[1])

        if not allow_resize:
            self.setFixedSize(self.width(), self.height())

    def loadURL(self, url: str):
        self.browser.load(QUrl(url))

    def onLoadFinished(self):
        self.setWindowTitle(self.tw_title)

    def closeEvent(self, event):
        self.setWindowTitle(f"{self.tw_title} (Closing...)")
        self.browser.deleteLater()
        self.browser.stop()
        self.browser.destroy()
        del self.browser
        self.close()
        QCoreApplication.quit()

def run_web_app(
    url: str,
    title: str = "Hi Stack Overflow",
    width: int = 900,
    height: int = 600,
    allow_resize: bool = True,
):
    qt_app = QtWidgets.QApplication.instance()

    if qt_app is None:
        qt_app = QApplication(sys.argv)

    qt_app.setQuitOnLastWindowClosed(True)

    window = MainWindowWeb()
    window.setParams(
        title=title, window_size=(width, height), allow_resize=allow_resize
    )
    window.loadURL(url)   # <--- crashes here
    window.show()
    err = qt_app.exec_()

    del window
    del qt_app

    return err

run_web_app()

# do stuff

run_web_app()

# do stuff

run_web_app()
Daniel R. Livingston
  • 1,227
  • 14
  • 36
  • 1
    Can I ask you why you don't want to reuse an existing application instance? – musicamante Sep 14 '21 at 02:13
  • Because it doesn't make sense in the context of the code - visualization is entirely optional and secondary to its main purpose – Daniel R. Livingston Sep 14 '21 at 04:08
  • 2
    I understand that the choice may be open to opinion, but *if* the app can possibly be reused, I see little benefit in continuously closing and opening it again. Can you give more context that would explain why not keeping alive the existing instance (if any, otherwise it would be created) would be actually better? – musicamante Sep 14 '21 at 05:29
  • Imagine the Python module NumPy had an option to visualize an ndarray. By and large, you won't use that visualization option in your normal workflow, but occasionally you might. There's a similar context in my case. Given that, do you have any advice? – Daniel R. Livingston Sep 14 '21 at 19:06
  • 1
    I don't know what possible workflows you have in mind, but since we're talking about possibly big amounts of data, which require decently fast computers with decent amounts of RAM (and I also mean a <10 years old computer can be enough). With that in mind, the amount of resources occupied by a persistent QApplication is negligible, and you don't even need to load it everytime: you can just query for the existing instance and create a new if there's none. If you're preouccupied by the *memory* used by widgets, what you could do is to allow the possibility to automatically *delete* all widgets. – musicamante Sep 14 '21 at 21:39
  • 1
    As the link explains, the current QApplication, even after calling `quit`, has to be deleted *at the right time*, and deleting the python wrapper might not be enough. Keeping the same instance is better and simpler, and if you want to be sure that all widgets are properly deleted, set the `Qt.WA_DeleteOnClose` attribute on the widgets that can be deleted on close without issues, and finally close/delete all remaining `QApplication.topLevelWidgets` when you want to "close" your UI session. Deleting their python references is possible, but I think it has negligible impact on memory. – musicamante Sep 14 '21 at 21:44

0 Answers0