在调用 deleteLater() 后直接删除对 Qt 对象的 Python 引用是否安全? [英] Is it safe to remove a Python reference to a Qt object directly after calling deleteLater()?

查看:135
本文介绍了在调用 deleteLater() 后直接删除对 Qt 对象的 Python 引用是否安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑下面的最小示例,它实现了一个自定义 QNetworkAccessManager,它维护了一个未完成的 QNetworkReply 实例列表.

Please consider the minimal example below, which implements a custom QNetworkAccessManager that maintains a list of unfinished QNetworkReply instances.

当回复finished时,它会从unfinished_replies列表中删除.

When a reply is finished, it is removed from the unfinished_replies list.

PyQt/PySide 中是否需要 deleteLater() 中所述?QNetworkReply.deleteLater() 用于在 finished 槽内安排要删除的 Qt 对象.

As discussed in Is deleteLater() necessary in PyQt/PySide?, QNetworkReply.deleteLater() is used, inside the finished slot, to schedule the Qt object for deletion.

但是,我不确定删除对回复对象的 Python 引用的最佳方法是什么.我可以想到两个(互斥的)删除 Python 引用的选项,如下例所示:

However, I am not sure what would be the best way to remove the Python reference to the reply object. I can think of two (mutually exclusive) options for removing the Python reference, as shown in the example below:

  1. 调用deleteLater()

在发出 QNetworkReply.destroyed 信号时移除 (文档)

remove when the QNetworkReply.destroyed signal is emitted (docs)

这两个选项似乎都可以正常工作.我更喜欢选项 1,但我不确定这是否会在极少数情况下导致意外.哪个最好?或者还有其他选择吗?

Both options seem to work just fine. I would prefer option 1, but I'm not sure if this could lead to surprises in rare cases. Which would be best? Or is there another alternative?

import sys
from PyQt5 import QtNetwork, QtWidgets, QtCore


class CustomNetworkAccessManager(QtNetwork.QNetworkAccessManager):
    def __init__(self):
        super(CustomNetworkAccessManager, self).__init__()
        self.unfinished_replies = []
        self.finished.connect(self.slot)

    def get(self, *args, **kwargs):
        reply = super(CustomNetworkAccessManager, self).get(*args, **kwargs)
        reply.index = i  # just for printing
        self.unfinished_replies.append(reply)

    def remove_from_list(self, reply):
        self.unfinished_replies.remove(reply)
        print('{} unfinished replies left'.format(len(self.unfinished_replies)))
        if not self.unfinished_replies:
            QtCore.QCoreApplication.quit()

    def slot(self, reply):
        print('reply {} finished'.format(reply.index))
        # handle the Qt side:
        reply.deleteLater()  
        # handle the Python side:
        # either
        # OPTION 1 - remove now
        self.remove_from_list(reply)
        # or 
        # OPTION 2 - remove when destroyed
        # reply.destroyed.connect(lambda: self.remove_from_list(reply))


if __name__ == '__main__':
    # Initialize
    app = QtWidgets.QApplication(sys.argv)
    manager = CustomNetworkAccessManager()

    # Schedule requests
    url = 'http://httpbin.org/get'
    for i in range(6):
        manager.get(QtNetwork.QNetworkRequest(QtCore.QUrl(url)))

    # Start event loop
    app.exec_()

附言抱歉 Python 2 代码

p.s. sorry for the Python 2 code

推荐答案

两者是等价的,只是在被移除的那一刻不同.但是要更详细地了解您必须了解 PyQt5/PySide2 绑定的工作原理.这些库在 C++ 对象周围创建了一个包装器,例如:

Both are equivalent, they only differ the moment they are removed. But to understand more in detail you have to understand how the PyQt5/PySide2 binding works. Those libraries create a wrapper around the C++ object, something like:

class FooWrapper:
    def __new__(self, cls, *args, **kwargs):
         # ...
         instance = ...
         instance._cpp_object = create_cpp_object()
         # ...
         return instance

    def foo_method(self, *args, **kwargs):
        return execute_foo_method(self._cpp_object, *args, **kwargs)

    def __del__(self):
        destroyed_cpp_object(self._cpp_object)

因此,当调用 deleteLater 时,仅删除 cpp_object 而不是包装器,您可以验证是否使用:

So when calling deleteLater only the cpp_object is deleted and not the wrapper, you can verify that if you use:

reply.destroyed.connect(self.remove_from_list)

Traceback (most recent call last):
  File "main.py", line 32, in <lambda>
    reply.destroyed.connect(self.remove_from_list)
  File "main.py", line 17, in remove_from_list
    self.unfinished_replies.remove(reply)
ValueError: list.remove(x): x not in list

由于destroy 传递的参数是一个无效的包装器,因此会出现上述错误.对于这种情况,解决方案是使用 sip.isdeleted() 检查 cpp_object 是否已删除:

since the parameter passed by destroyed is an invalid wrapper getting the above error. For this case, a solution is to check if the cpp_object has been removed with sip.isdeleted():

from PyQt5 import QtNetwork, QtWidgets, QtCore
import sip

# ...
class CustomNetworkAccessManager(QtNetwork.QNetworkAccessManager):
    # ...

    def remove_from_list(self):
        self.unfinished_replies = [
            reply for reply in self.unfinished_replies if not sip.isdeleted(reply)
        ]
        print("{} unfinished replies left".format(len(self.unfinished_replies)))
        if not self.unfinished_replies:
            QtCore.QCoreApplication.quit()

    def slot(self, reply):
        print("reply {} finished".format(reply.index))
        # handle the Qt side:
        reply.deleteLater()
        # handle the Python side:
        reply.destroyed.connect(self.remove_from_list)

回到对你的方法的研究,这些可以绘制如下:

Returning to the study of your methods, these can be graphed as follows:

(FIRST METHOD)
------------┬------------------┬---------------------┬-----------------------
            |                  |                     |
 call_deleteLater remove_reply_from_list          destroyed

(SECOND METHOD)
------------┬-----------------------------------------┬-----------------┬------
            |                                         |                 |
 call_deleteLater                               destroyed remove_reply_from_list

而且由于您使用的是原始包装器,因此您应该没有任何问题.

And since you are using the original wrappers you should not have any problem.

结论:两者等效且安全.

这篇关于在调用 deleteLater() 后直接删除对 Qt 对象的 Python 引用是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆