关闭 QWebEngineView 警告 "Release of profile requested but WebEnginePage still not deleted. Expect troubles !"

Closing QWebEngineView warns "Release of profile requested but WebEnginePage still not deleted. Expect troubles !"

我制作了下面显示的 PlotlyViewer class 来显示 Plotly 图表,它工作正常但在我关闭它时显示此警告:Release of profile requested but WebEnginePage still not deleted. Expect troubles !

当我在 __init__ 中创建自己的 QWebEngineProfile 实例时开始出现警告,因此我可以连接到 downloadRequested 信号以显示保存文件对话框。 我让我的 PlotlyViewer 成为 QWebEnginePage 的父级,但听起来好像在父级关闭时它没有被清理?我不明白为什么。

import os
import tempfile

from plotly.io import to_html
import plotly.graph_objs as go
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets, sip, QtWebEngineWidgets
from PyQt5.QtCore import Qt

class PlotlyViewer(QtWebEngineWidgets.QWebEngineView):
    def __init__(self, fig=None):
        super().__init__()

        # 
        self.profile = QtWebEngineWidgets.QWebEngineProfile(self)
        self.page = QtWebEngineWidgets.QWebEnginePage(self.profile, self)
        self.setPage(self.page)
        self.profile.downloadRequested.connect(self.on_downloadRequested)

        # 
        self.temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False)
        self.set_figure(fig)

        self.resize(700, 600)
        self.setWindowTitle("Plotly Viewer")

    def set_figure(self, fig=None):
        self.temp_file.seek(0)

        if fig:
            self.temp_file.write(to_html(fig, config={"responsive": True}))
        else:
            self.temp_file.write("")

        self.temp_file.truncate()
        self.temp_file.seek(0)
        self.load(QtCore.QUrl.fromLocalFile(self.temp_file.name))

    def closeEvent(self, event: QtGui.QCloseEvent) -> None:
        self.temp_file.close()
        os.unlink(self.temp_file.name)

    def sizeHint(self) -> QtCore.QSize:
        return QtCore.QSize(400, 400)

    # 
    def on_downloadRequested(self, download):
        dialog = QtWidgets.QFileDialog()
        dialog.setDefaultSuffix(".png")
        path, _ = dialog.getSaveFileName(self, "Save File", os.path.join(os.getcwd(), "newplot.png"), "*.png")
        if path:
            download.setPath(path)
            download.accept()

if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    fig = go.Figure()
    fig.add_scatter(
        x=np.random.rand(100),
        y=np.random.rand(100),
        mode="markers",
        marker={
            "size": 30,
            "color": np.random.rand(100),
            "opacity": 0.6,
            "colorscale": "Viridis",
        },
    )

    pv = PlotlyViewer(fig)
    pv.show()
    app.exec_()

问题是python使用内存的方式不符合Qt的pre-established规则(这是绑定问题),也就是说,Qt希望删除QWebEnginePage首先,但 python 首先删除 QWebEngineProfile。

在您的情况下,我认为不需要创建与默认情况不同的 QWebEnginePage 或 QWebEngineProfile,但要默认获取 QWebEngineProfile:

class PlotlyViewer(QtWebEngineWidgets.QWebEngineView):
    def __init__(self, fig=None):
        super().__init__()
        <b>self.page().profile().downloadRequested.connect(self.on_downloadRequested)</b>

        # 
        self.temp_file = tempfile.NamedTemporaryFile(
            mode="w", suffix=".html", delete=False
        )
        self.set_figure(fig)

        self.resize(700, 600)
        self.setWindowTitle("Plotly Viewer")

QWebEngineView class 的关闭事件的自定义实现修复了该问题。

I re-adapted(并在 PyQt6 中使用其他一些代码 成功测试了 ,但问题相同)在 Qt forum.

中找到了答案
class PlotlyViewer(QWebEngineView):
    ...

    def closeEvent(self, qclose_event):
        """Overwrite QWidget method"""
        # Sets the accept flag of the event object, indicates that the event receiver wants the event.
        qclose_event.accept()
        # Schedules this object for deletion, QObject
        self.page().deleteLater()

参考资料