Is it safe to remove a Python reference to a Qt object directly after calling deleteLater()?

后端 未结 1 1904
有刺的猬
有刺的猬 2021-01-22 18:26

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

相关标签:
1条回答
  • 2021-01-22 18:50

    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)
    

    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
    

    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.

    Conclusion: Both are equivalent and safe.

    0 讨论(0)
提交回复
热议问题