Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Crash in simple PyQt5 app
Forum Updated to NodeBB v4.3 + New Features

Crash in simple PyQt5 app

Scheduled Pinned Locked Moved Solved Qt for Python
5 Posts 3 Posters 2.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    Andeksan
    wrote on last edited by Andeksan
    #1

    I have a small experience with python but enough with C++/Qt. For self study i decided to make a simple app and got troubles during developing. I have prepared sandbox which reproduces the crash. Most strange thing for me is how objects are released(sometimes C++ obj is released first then Py wrapper, or vice versa). but i do not know how to set right order for removing.

    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5 import *
    import sys
    
    def ListWidgetCompleterDestroyed(obj:QObject):
        print("<<>> ListWidgetCompleter C++ obj removed" + str(obj))
    
    class ListWidgetCompleter(QObject):
        def __init__(self, parent: QLineEdit):
            QObject.__init__(self, parent)
            assert parent != None
            self.destroyed.connect(ListWidgetCompleterDestroyed)
    
            self.parent().installEventFilter(self)
    
            self.__popup = None
            self.__maxVisibaleItems = 0
    
        def __del__(self):
            print("<<>> ListWidgetCompleter py layer deleted")
    
        def setPopup(self, listWidget: QListWidget):
            assert listWidget != None
            if self.__popup != listWidget and self.__popup != None:
                self.__popup.deleteLater()
    
            self.__popup = listWidget
            self.__popup.setParent(None)
            self.__popup.setWindowFlag(Qt.Popup)
            self.__popup.installEventFilter(self)
            self.__popup.setFocusProxy(self.parent())
            self.__popup.setFocusPolicy(self.parent().focusPolicy())
    
        def setMaxVisibleItems(self, maxItems: int):
            self.__maxVisibaleItems = maxItems
    
        def popup(self):
            return self.__popup
    
        def showPopup(self):
            lineEdit = self.parent()
    
            pos = lineEdit.mapToGlobal(QPoint(0, 0))
            print("X " + str(pos.x()) + " Y " + str(pos.y()) + " le.w " + str(lineEdit.width()))
            
            self.__popup.setGeometry(pos.x(), pos.y() + lineEdit.height(), lineEdit.width(), 80)
    
            if not self.__popup.isVisible():
                self.__popup.show()
            
            self.__popup.setFocus()
    
        def eventFilter(self, o: QObject, e: QEvent):
    
            lineEdit: QLineEdit = self.parent()
            popup_ = self.popup()
            if lineEdit == o:
                pass
            else:
                if e.type() == QEvent.KeyPress:
                    print("keypress")
                    lineEdit.setFocus()
                    lineEdit.event(e)
                    return False
            return QObject.eventFilter(self, o, e)
    
    def MyLineEditDestroyed(obj:QObject):
        print("<<>> MyLineEdit C++ obj removed" + str(obj))
    
    class MyLineEdit(QLineEdit):
        def __init__(self, parent = None):
            QLineEdit.__init__(self, parent)
            self.destroyed.connect(MyLineEditDestroyed)
        def __del__(self):
            print("<<>> MyLineEdit py layer deleted")
    
    def DicViewDestroyed(obj:QObject):
        print("<<>> DicView C++ obj removed" + str(obj))
    
    class DicView(QWidget):
        def __init__(self, parent = None):
            QWidget.__init__(self, parent)
            self.destroyed.connect(DicViewDestroyed)
            vbox = QVBoxLayout(self)
            self.lineEidt = MyLineEdit()
            vbox.addWidget(self.lineEidt)
    
            self.inputCompleter = ListWidgetCompleter(self.lineEidt)
            self.inputCompleter.setPopup(QWidget())
    
        def __del__(self):
            print("<<>> DicView py layer deleted")
    
        def showEvent(self, event):
            print("showEvent " + type(self).__name__)
            QWidget.showEvent(self, event)
            QTimer.singleShot(1, self.inputCompleter.showPopup)
    
    class MyWindow(QWidget):
        def __init__(self, parent = None):
            QWidget.__init__(self, parent)
    
            self.vbox = QVBoxLayout(self)
            btn = QPushButton(self)
            self.vbox.addWidget(btn)
            self.dicView = DicView(self)
            self.vbox.addWidget(self.dicView)
    
            btn.setText("remove Me")
            btn.clicked.connect(self.onClick)
        
        def onClick(self):
            self.vbox.removeWidget(self.dicView)
            self.dicView.deleteLater()
            self.dicView = None
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
    
        win = MyWindow()
    
        win.show()
        sys.exit(app.exec_())
    
        print('APP exited')
    

    traces:
    Python 3.9.1
    python main.py
    showEvent DicView
    X 660 Y 329 le.w 160
    <<>> DicView py layer deleted
    <<>> DicView C++ obj removed<PyQt5.QtWidgets.QWidget object at 0x0000024C99083CA0>
    <<>> MyLineEdit C++ obj removed<PyQt5.QtWidgets.QWidget object at 0x0000024C99083CA0>
    <<>> ListWidgetCompleter py layer deleted
    Traceback (most recent call last):
    File "C:\Users\o.kryvoruchko\Andeksan\System\Python\gui\qt\completer\main.py", line 57, in eventFilter
    lineEdit: QLineEdit = self.parent()
    RuntimeError: wrapped C/C++ object of type ListWidgetCompleter has been deleted

    some ideas how to set a right order or fix crash?

    JonBJ 1 Reply Last reply
    0
    • A Andeksan

      I have a small experience with python but enough with C++/Qt. For self study i decided to make a simple app and got troubles during developing. I have prepared sandbox which reproduces the crash. Most strange thing for me is how objects are released(sometimes C++ obj is released first then Py wrapper, or vice versa). but i do not know how to set right order for removing.

      from PyQt5.QtWidgets import *
      from PyQt5.QtCore import *
      from PyQt5 import *
      import sys
      
      def ListWidgetCompleterDestroyed(obj:QObject):
          print("<<>> ListWidgetCompleter C++ obj removed" + str(obj))
      
      class ListWidgetCompleter(QObject):
          def __init__(self, parent: QLineEdit):
              QObject.__init__(self, parent)
              assert parent != None
              self.destroyed.connect(ListWidgetCompleterDestroyed)
      
              self.parent().installEventFilter(self)
      
              self.__popup = None
              self.__maxVisibaleItems = 0
      
          def __del__(self):
              print("<<>> ListWidgetCompleter py layer deleted")
      
          def setPopup(self, listWidget: QListWidget):
              assert listWidget != None
              if self.__popup != listWidget and self.__popup != None:
                  self.__popup.deleteLater()
      
              self.__popup = listWidget
              self.__popup.setParent(None)
              self.__popup.setWindowFlag(Qt.Popup)
              self.__popup.installEventFilter(self)
              self.__popup.setFocusProxy(self.parent())
              self.__popup.setFocusPolicy(self.parent().focusPolicy())
      
          def setMaxVisibleItems(self, maxItems: int):
              self.__maxVisibaleItems = maxItems
      
          def popup(self):
              return self.__popup
      
          def showPopup(self):
              lineEdit = self.parent()
      
              pos = lineEdit.mapToGlobal(QPoint(0, 0))
              print("X " + str(pos.x()) + " Y " + str(pos.y()) + " le.w " + str(lineEdit.width()))
              
              self.__popup.setGeometry(pos.x(), pos.y() + lineEdit.height(), lineEdit.width(), 80)
      
              if not self.__popup.isVisible():
                  self.__popup.show()
              
              self.__popup.setFocus()
      
          def eventFilter(self, o: QObject, e: QEvent):
      
              lineEdit: QLineEdit = self.parent()
              popup_ = self.popup()
              if lineEdit == o:
                  pass
              else:
                  if e.type() == QEvent.KeyPress:
                      print("keypress")
                      lineEdit.setFocus()
                      lineEdit.event(e)
                      return False
              return QObject.eventFilter(self, o, e)
      
      def MyLineEditDestroyed(obj:QObject):
          print("<<>> MyLineEdit C++ obj removed" + str(obj))
      
      class MyLineEdit(QLineEdit):
          def __init__(self, parent = None):
              QLineEdit.__init__(self, parent)
              self.destroyed.connect(MyLineEditDestroyed)
          def __del__(self):
              print("<<>> MyLineEdit py layer deleted")
      
      def DicViewDestroyed(obj:QObject):
          print("<<>> DicView C++ obj removed" + str(obj))
      
      class DicView(QWidget):
          def __init__(self, parent = None):
              QWidget.__init__(self, parent)
              self.destroyed.connect(DicViewDestroyed)
              vbox = QVBoxLayout(self)
              self.lineEidt = MyLineEdit()
              vbox.addWidget(self.lineEidt)
      
              self.inputCompleter = ListWidgetCompleter(self.lineEidt)
              self.inputCompleter.setPopup(QWidget())
      
          def __del__(self):
              print("<<>> DicView py layer deleted")
      
          def showEvent(self, event):
              print("showEvent " + type(self).__name__)
              QWidget.showEvent(self, event)
              QTimer.singleShot(1, self.inputCompleter.showPopup)
      
      class MyWindow(QWidget):
          def __init__(self, parent = None):
              QWidget.__init__(self, parent)
      
              self.vbox = QVBoxLayout(self)
              btn = QPushButton(self)
              self.vbox.addWidget(btn)
              self.dicView = DicView(self)
              self.vbox.addWidget(self.dicView)
      
              btn.setText("remove Me")
              btn.clicked.connect(self.onClick)
          
          def onClick(self):
              self.vbox.removeWidget(self.dicView)
              self.dicView.deleteLater()
              self.dicView = None
      
      if __name__ == "__main__":
          app = QApplication(sys.argv)
      
          win = MyWindow()
      
          win.show()
          sys.exit(app.exec_())
      
          print('APP exited')
      

      traces:
      Python 3.9.1
      python main.py
      showEvent DicView
      X 660 Y 329 le.w 160
      <<>> DicView py layer deleted
      <<>> DicView C++ obj removed<PyQt5.QtWidgets.QWidget object at 0x0000024C99083CA0>
      <<>> MyLineEdit C++ obj removed<PyQt5.QtWidgets.QWidget object at 0x0000024C99083CA0>
      <<>> ListWidgetCompleter py layer deleted
      Traceback (most recent call last):
      File "C:\Users\o.kryvoruchko\Andeksan\System\Python\gui\qt\completer\main.py", line 57, in eventFilter
      lineEdit: QLineEdit = self.parent()
      RuntimeError: wrapped C/C++ object of type ListWidgetCompleter has been deleted

      some ideas how to set a right order or fix crash?

      JonBJ Online
      JonBJ Online
      JonB
      wrote on last edited by
      #2

      @Andeksan
      I'm not sure but I observe the following.

              self.dicView = DicView(self)
              self.vbox.addWidget(self.dicView)
      
          def onClick(self):
              self.dicView.deleteLater()
              self.dicView = None
      

      You deleteLater()/set to None the DicView widget, but you do not explicitly remove it from the vbox layout it is on. I would self.vbox.removeWidget(self.dicView) before deleting it. Else I think the layout now still has a deleted widget on it.

      I don't know if that is the cause of your problem, but worth a try.

      Otherwise I suggest you reduce your code to something considerably smaller.

      I will say that in a whole PyQt5 application I worked on I only have a single deleteLater(). You mostly just don't have to do that, because Python reference counting deals with it.

      As a hint, you can use QtWidgets.QApplication.allWidgets() to discover what widgets are still around, if you want to know what has/has not been destroyed. I wrote the following utility function to tell me this:

      def debugWidgetsInUse():
          # Function to allow to see what widgets are currently existing/in use from Qt
          # This can be used to help to detect if there are any "leaks"
      
          allWidgets = QtWidgets.QApplication.allWidgets()
          errfunctions.uiLogger.debug("Existing widgets: {}".format(len(allWidgets)))
      
          allDialogs = [w for w in allWidgets if isinstance(w, QtWidgets.QDialog)]
          for dlg in allDialogs:
              errfunctions.uiLogger.debug("Existing dialog: {} ({}) ({})".format(
                  str(type(dlg)), dlg.windowTitle(), "visible" if dlg.isVisible() else "invisible"))
      
          allOrphans = [
              w for w in allWidgets
              if w.parent() is None
              and not isinstance(w, QtWidgets.QDesktopWidget)
              and not isinstance(w, QtWidgets.QMainWindow)
          ]
          for w in allOrphans:
              text = w.objectName()
              if text == "":
                  if hasattr(w, "text"):
                      text = w.text()
              descendants = [
                  descendant for descendant in allWidgets
                  if descendant != w and w.isAncestorOf(descendant)
              ]
              errfunctions.uiLogger.debug("Orphan widget: {} ({}) ({} descendant(s))".format(
                  str(type(w)), text, len(descendants)))
      
      
      A eyllanescE 2 Replies Last reply
      2
      • JonBJ JonB

        @Andeksan
        I'm not sure but I observe the following.

                self.dicView = DicView(self)
                self.vbox.addWidget(self.dicView)
        
            def onClick(self):
                self.dicView.deleteLater()
                self.dicView = None
        

        You deleteLater()/set to None the DicView widget, but you do not explicitly remove it from the vbox layout it is on. I would self.vbox.removeWidget(self.dicView) before deleting it. Else I think the layout now still has a deleted widget on it.

        I don't know if that is the cause of your problem, but worth a try.

        Otherwise I suggest you reduce your code to something considerably smaller.

        I will say that in a whole PyQt5 application I worked on I only have a single deleteLater(). You mostly just don't have to do that, because Python reference counting deals with it.

        As a hint, you can use QtWidgets.QApplication.allWidgets() to discover what widgets are still around, if you want to know what has/has not been destroyed. I wrote the following utility function to tell me this:

        def debugWidgetsInUse():
            # Function to allow to see what widgets are currently existing/in use from Qt
            # This can be used to help to detect if there are any "leaks"
        
            allWidgets = QtWidgets.QApplication.allWidgets()
            errfunctions.uiLogger.debug("Existing widgets: {}".format(len(allWidgets)))
        
            allDialogs = [w for w in allWidgets if isinstance(w, QtWidgets.QDialog)]
            for dlg in allDialogs:
                errfunctions.uiLogger.debug("Existing dialog: {} ({}) ({})".format(
                    str(type(dlg)), dlg.windowTitle(), "visible" if dlg.isVisible() else "invisible"))
        
            allOrphans = [
                w for w in allWidgets
                if w.parent() is None
                and not isinstance(w, QtWidgets.QDesktopWidget)
                and not isinstance(w, QtWidgets.QMainWindow)
            ]
            for w in allOrphans:
                text = w.objectName()
                if text == "":
                    if hasattr(w, "text"):
                        text = w.text()
                descendants = [
                    descendant for descendant in allWidgets
                    if descendant != w and w.isAncestorOf(descendant)
                ]
                errfunctions.uiLogger.debug("Orphan widget: {} ({}) ({} descendant(s))".format(
                    str(type(w)), text, len(descendants)))
        
        
        A Offline
        A Offline
        Andeksan
        wrote on last edited by
        #3

        @JonB actually i forgot that my main project has this call self.vbox.removeWidget(self.dicView) before deleteLater. But even with it, sandbox prj still has problem. I have updated my first post. to be more precise.

        I will think how to simplify this simple project. for present moment i have not to much ideas.(

        I understand that Python should take care about object life. and if remove deleteLater i will get crashes during closing app(case for the main project). As for me better to see immediately what view causes crash.
        Also for me interesting how qt python projects deal with that fact that destroying object can be as on python and C++ side in different order.

        1 Reply Last reply
        0
        • JonBJ JonB

          @Andeksan
          I'm not sure but I observe the following.

                  self.dicView = DicView(self)
                  self.vbox.addWidget(self.dicView)
          
              def onClick(self):
                  self.dicView.deleteLater()
                  self.dicView = None
          

          You deleteLater()/set to None the DicView widget, but you do not explicitly remove it from the vbox layout it is on. I would self.vbox.removeWidget(self.dicView) before deleting it. Else I think the layout now still has a deleted widget on it.

          I don't know if that is the cause of your problem, but worth a try.

          Otherwise I suggest you reduce your code to something considerably smaller.

          I will say that in a whole PyQt5 application I worked on I only have a single deleteLater(). You mostly just don't have to do that, because Python reference counting deals with it.

          As a hint, you can use QtWidgets.QApplication.allWidgets() to discover what widgets are still around, if you want to know what has/has not been destroyed. I wrote the following utility function to tell me this:

          def debugWidgetsInUse():
              # Function to allow to see what widgets are currently existing/in use from Qt
              # This can be used to help to detect if there are any "leaks"
          
              allWidgets = QtWidgets.QApplication.allWidgets()
              errfunctions.uiLogger.debug("Existing widgets: {}".format(len(allWidgets)))
          
              allDialogs = [w for w in allWidgets if isinstance(w, QtWidgets.QDialog)]
              for dlg in allDialogs:
                  errfunctions.uiLogger.debug("Existing dialog: {} ({}) ({})".format(
                      str(type(dlg)), dlg.windowTitle(), "visible" if dlg.isVisible() else "invisible"))
          
              allOrphans = [
                  w for w in allWidgets
                  if w.parent() is None
                  and not isinstance(w, QtWidgets.QDesktopWidget)
                  and not isinstance(w, QtWidgets.QMainWindow)
              ]
              for w in allOrphans:
                  text = w.objectName()
                  if text == "":
                      if hasattr(w, "text"):
                          text = w.text()
                  descendants = [
                      descendant for descendant in allWidgets
                      if descendant != w and w.isAncestorOf(descendant)
                  ]
                  errfunctions.uiLogger.debug("Orphan widget: {} ({}) ({} descendant(s))".format(
                      str(type(w)), text, len(descendants)))
          
          
          eyllanescE Offline
          eyllanescE Offline
          eyllanesc
          wrote on last edited by
          #4

          @JonB If deleteLater() is used then it is not necessary to use removeWidget since the layout will be notified if any of the child widgets are removed and then it will remove them first.

          If you want me to help you develop some work then you can write to my email: e.yllanescucho@gmal.com.

          1 Reply Last reply
          1
          • A Offline
            A Offline
            Andeksan
            wrote on last edited by
            #5

            ok, at first glance i fixed the crash, now app destroys objects correctly

            class ListWidgetCompleter(QObject):
                def __init__(self, popup: QWidget, widget: QWidget):
                    QObject.__init__(self, None)
                    assert popup != None
                    assert widget != None
            
                    self.destroyed.connect(ListWidgetCompleterDestroed)
                    # assert isinstance(parent, QLineEdit)
            
                    self.__maxVisibaleItems = 0
                    self.__attachPoupToWidget(popup, widget)
            
                def __del__(self):
                    print("<<>> ListWidgetCompleter py layer deleted")
            
                def __attachPoupToWidget(self, popup: QWidget, widget: QWidget):
                    self.__popup = popup
                    self.__widget = widget
            
                    self.__popup.setParent(None)
                    self.__popup.setWindowFlag(Qt.Popup)
            
                    self.__popup.installEventFilter(self)
                    self.__widget.installEventFilter(self)
            
                    self.__popup.setFocusProxy(self.__widget)
                    self.__popup.setFocusPolicy(self.__widget.focusPolicy())
            
                def setMaxVisibleItems(self, maxItems: int):
                    self.__maxVisibaleItems = maxItems
            
                def popup(self):
                    return self.__popup
            
                def widget(self):
                    return self.__widget
            
                def showPopup(self, rect):
            
                    pos = self.__widget.mapToGlobal(QPoint(0, 0))
                    print("X " + str(pos.x()) + " Y " + str(pos.y()) + " le.w " + str(self.__widget.width()))
                    self.__popup.setGeometry(pos.x(), pos.y() + self.__widget.height(), self.__widget.width(), 80)
            
                    if not self.__popup.isVisible():
                        self.__popup.show()
                    
                    self.__popup.setFocus()
            
                def eventFilter(self, o: QObject, e: QEvent):
            
                    if self.__widget == o:
                        pass
                    else:
                        if e.type() == QEvent.KeyPress:
                            print("keypress")
                            self.__widget.setFocus()
                            self.__widget.event(e)
                            return False
            
                    return QObject.eventFilter(self, o, e)
            

            <<>> DicView py layer deleted
            <<>> ListWidgetCompleter py layer deleted
            <<>> ListWidgetCompleter C++ obj removed<PyQt5.QtCore.QObject object at 0x000002345A1C88B0>
            <<>> SuggestionPopup py layer deleted
            <<>> SuggestionPopup C++ obj removed<PyQt5.QtWidgets.QWidget object at 0x000002345A1C88B0>
            <<>> DicView C++ obj removed<PyQt5.QtWidgets.QWidget object at 0x000002345A194EE0>

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved