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. Memory can not release correctly because of using lambda as slot
Forum Updated to NodeBB v4.3 + New Features

Memory can not release correctly because of using lambda as slot

Scheduled Pinned Locked Moved Solved Qt for Python
16 Posts 5 Posters 2.2k Views 2 Watching
  • 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.
  • feiyuhuahuoF Offline
    feiyuhuahuoF Offline
    feiyuhuahuo
    wrote on last edited by
    #1

    The code:

    from PySide6.QtWidgets import QVBoxLayout, QPushButton, QWidget
    from PySide6.QtWidgets import QApplication, QLabel, QMainWindow
    from PySide6.QtCore import Qt, QPointF
    from PySide6.QtGui import QPixmap, QPainter, QAction
    
    
    class BaseImgFrame(QLabel):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.painter = QPainter()
            self.resize(512, 512)
            self.img, self.scaled_img = None, None
            self.img_tl = QPointF(0., 0.)
            self.action_bilinear = QAction(self.tr('缩放'), self)
            self.action_bilinear.triggered.connect(lambda: self.fake_slot('hello'))
    
        def paintEvent(self, e):
            self.painter.begin(self)
            self.painter.drawPixmap(self.img_tl, self.scaled_img)
            self.painter.end()
    
        def paint_img(self, img_path_or_pix_map):
            self.img = QPixmap(img_path_or_pix_map)
            self.scaled_img = self.img.scaled(self.size(), Qt.KeepAspectRatio, Qt.FastTransformation)
            self.update()
    
        def fake_slot(self, value='asd'):
            print(value)
    
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            if not MainWindow.objectName():
                MainWindow.setObjectName(u"MainWindow")
            MainWindow.resize(573, 584)
            self.centralwidget = QWidget(MainWindow)
            self.centralwidget.setObjectName(u"centralwidget")
            self.verticalLayout = QVBoxLayout(self.centralwidget)
            self.verticalLayout.setSpacing(6)
            self.verticalLayout.setObjectName(u"verticalLayout")
            self.verticalLayout.setContentsMargins(0, 0, 0, 0)
            self.img_area = BaseImgFrame(self.centralwidget)
            self.img_area.setObjectName(u"img_area")
            self.verticalLayout.addWidget(self.img_area)
            MainWindow.setCentralWidget(self.centralwidget)
    
    
    class BaseImgWindow(QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.ui = Ui_MainWindow()
            self.ui.setupUi(self)
            self.resize(512, 512)
    
        def paint_img(self, img_path_or_pix_map):
            self.ui.img_area.paint_img(img_path_or_pix_map)
    
    
    class ImageDisplay(QMainWindow):
        def __init__(self):
            super().__init__()
            self.central_widget = QWidget(self)
            self.setCentralWidget(self.central_widget)
            lay = QVBoxLayout(self.central_widget)
            self.button = QPushButton(self)
            lay.addWidget(self.button)
            self.central_widget.setLayout(lay)
            self.button.clicked.connect(self.add)
    
        def add(self):
            window_new_img = BaseImgWindow(self)
            window_new_img.setAttribute(Qt.WA_DeleteOnClose)
            window_new_img.paint_img(QPixmap('D:\Data\yyyy\原图/8.10.jpg'))
            window_new_img.show()
    
    
    if __name__ == '__main__':
        app = QApplication()
        window = ImageDisplay()
        window.show()
        app.exec()
    

    屏幕截图 2023-11-15 161351.png
    屏幕截图 2023-11-15 161416.png
    屏幕截图 2023-11-15 161527.png
    Run the code and a button will show. Click the button and a new window will show. self.action_bilinear is connected to a fake slot. After the window is closed, the memory can not be correctly released. The memory consumption is from the upper image to the middle image before and after closing the window. But if I change self.action_bilinear.triggered.connect(lambda: self.fake_slot('hello')) to self.action_bilinear.triggered.connect(self.fake_slot), the memory consumption is from the upper image to the lower image before and after closing the window, which means the memory is released correctly. Better test with a large image.
    The PySide6 version is 6.5.2 and OS is Windows11.

    JonBJ JoeCFDJ 2 Replies Last reply
    0
    • feiyuhuahuoF feiyuhuahuo referenced this topic on
    • feiyuhuahuoF feiyuhuahuo

      The code:

      from PySide6.QtWidgets import QVBoxLayout, QPushButton, QWidget
      from PySide6.QtWidgets import QApplication, QLabel, QMainWindow
      from PySide6.QtCore import Qt, QPointF
      from PySide6.QtGui import QPixmap, QPainter, QAction
      
      
      class BaseImgFrame(QLabel):
          def __init__(self, parent=None):
              super().__init__(parent)
              self.painter = QPainter()
              self.resize(512, 512)
              self.img, self.scaled_img = None, None
              self.img_tl = QPointF(0., 0.)
              self.action_bilinear = QAction(self.tr('缩放'), self)
              self.action_bilinear.triggered.connect(lambda: self.fake_slot('hello'))
      
          def paintEvent(self, e):
              self.painter.begin(self)
              self.painter.drawPixmap(self.img_tl, self.scaled_img)
              self.painter.end()
      
          def paint_img(self, img_path_or_pix_map):
              self.img = QPixmap(img_path_or_pix_map)
              self.scaled_img = self.img.scaled(self.size(), Qt.KeepAspectRatio, Qt.FastTransformation)
              self.update()
      
          def fake_slot(self, value='asd'):
              print(value)
      
      
      class Ui_MainWindow(object):
          def setupUi(self, MainWindow):
              if not MainWindow.objectName():
                  MainWindow.setObjectName(u"MainWindow")
              MainWindow.resize(573, 584)
              self.centralwidget = QWidget(MainWindow)
              self.centralwidget.setObjectName(u"centralwidget")
              self.verticalLayout = QVBoxLayout(self.centralwidget)
              self.verticalLayout.setSpacing(6)
              self.verticalLayout.setObjectName(u"verticalLayout")
              self.verticalLayout.setContentsMargins(0, 0, 0, 0)
              self.img_area = BaseImgFrame(self.centralwidget)
              self.img_area.setObjectName(u"img_area")
              self.verticalLayout.addWidget(self.img_area)
              MainWindow.setCentralWidget(self.centralwidget)
      
      
      class BaseImgWindow(QMainWindow):
          def __init__(self, parent=None):
              super().__init__(parent)
      
              self.ui = Ui_MainWindow()
              self.ui.setupUi(self)
              self.resize(512, 512)
      
          def paint_img(self, img_path_or_pix_map):
              self.ui.img_area.paint_img(img_path_or_pix_map)
      
      
      class ImageDisplay(QMainWindow):
          def __init__(self):
              super().__init__()
              self.central_widget = QWidget(self)
              self.setCentralWidget(self.central_widget)
              lay = QVBoxLayout(self.central_widget)
              self.button = QPushButton(self)
              lay.addWidget(self.button)
              self.central_widget.setLayout(lay)
              self.button.clicked.connect(self.add)
      
          def add(self):
              window_new_img = BaseImgWindow(self)
              window_new_img.setAttribute(Qt.WA_DeleteOnClose)
              window_new_img.paint_img(QPixmap('D:\Data\yyyy\原图/8.10.jpg'))
              window_new_img.show()
      
      
      if __name__ == '__main__':
          app = QApplication()
          window = ImageDisplay()
          window.show()
          app.exec()
      

      屏幕截图 2023-11-15 161351.png
      屏幕截图 2023-11-15 161416.png
      屏幕截图 2023-11-15 161527.png
      Run the code and a button will show. Click the button and a new window will show. self.action_bilinear is connected to a fake slot. After the window is closed, the memory can not be correctly released. The memory consumption is from the upper image to the middle image before and after closing the window. But if I change self.action_bilinear.triggered.connect(lambda: self.fake_slot('hello')) to self.action_bilinear.triggered.connect(self.fake_slot), the memory consumption is from the upper image to the lower image before and after closing the window, which means the memory is released correctly. Better test with a large image.
      The PySide6 version is 6.5.2 and OS is Windows11.

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

      @feiyuhuahuo
      The behaviour you report is expected from Python lambdas, nothing to do with Qt. Python does not know when to release lambdas from its reference-counting algorithm. If you Google for this you should find discussions on this topic.

      1 Reply Last reply
      1
      • feiyuhuahuoF feiyuhuahuo has marked this topic as solved on
      • feiyuhuahuoF feiyuhuahuo

        The code:

        from PySide6.QtWidgets import QVBoxLayout, QPushButton, QWidget
        from PySide6.QtWidgets import QApplication, QLabel, QMainWindow
        from PySide6.QtCore import Qt, QPointF
        from PySide6.QtGui import QPixmap, QPainter, QAction
        
        
        class BaseImgFrame(QLabel):
            def __init__(self, parent=None):
                super().__init__(parent)
                self.painter = QPainter()
                self.resize(512, 512)
                self.img, self.scaled_img = None, None
                self.img_tl = QPointF(0., 0.)
                self.action_bilinear = QAction(self.tr('缩放'), self)
                self.action_bilinear.triggered.connect(lambda: self.fake_slot('hello'))
        
            def paintEvent(self, e):
                self.painter.begin(self)
                self.painter.drawPixmap(self.img_tl, self.scaled_img)
                self.painter.end()
        
            def paint_img(self, img_path_or_pix_map):
                self.img = QPixmap(img_path_or_pix_map)
                self.scaled_img = self.img.scaled(self.size(), Qt.KeepAspectRatio, Qt.FastTransformation)
                self.update()
        
            def fake_slot(self, value='asd'):
                print(value)
        
        
        class Ui_MainWindow(object):
            def setupUi(self, MainWindow):
                if not MainWindow.objectName():
                    MainWindow.setObjectName(u"MainWindow")
                MainWindow.resize(573, 584)
                self.centralwidget = QWidget(MainWindow)
                self.centralwidget.setObjectName(u"centralwidget")
                self.verticalLayout = QVBoxLayout(self.centralwidget)
                self.verticalLayout.setSpacing(6)
                self.verticalLayout.setObjectName(u"verticalLayout")
                self.verticalLayout.setContentsMargins(0, 0, 0, 0)
                self.img_area = BaseImgFrame(self.centralwidget)
                self.img_area.setObjectName(u"img_area")
                self.verticalLayout.addWidget(self.img_area)
                MainWindow.setCentralWidget(self.centralwidget)
        
        
        class BaseImgWindow(QMainWindow):
            def __init__(self, parent=None):
                super().__init__(parent)
        
                self.ui = Ui_MainWindow()
                self.ui.setupUi(self)
                self.resize(512, 512)
        
            def paint_img(self, img_path_or_pix_map):
                self.ui.img_area.paint_img(img_path_or_pix_map)
        
        
        class ImageDisplay(QMainWindow):
            def __init__(self):
                super().__init__()
                self.central_widget = QWidget(self)
                self.setCentralWidget(self.central_widget)
                lay = QVBoxLayout(self.central_widget)
                self.button = QPushButton(self)
                lay.addWidget(self.button)
                self.central_widget.setLayout(lay)
                self.button.clicked.connect(self.add)
        
            def add(self):
                window_new_img = BaseImgWindow(self)
                window_new_img.setAttribute(Qt.WA_DeleteOnClose)
                window_new_img.paint_img(QPixmap('D:\Data\yyyy\原图/8.10.jpg'))
                window_new_img.show()
        
        
        if __name__ == '__main__':
            app = QApplication()
            window = ImageDisplay()
            window.show()
            app.exec()
        

        屏幕截图 2023-11-15 161351.png
        屏幕截图 2023-11-15 161416.png
        屏幕截图 2023-11-15 161527.png
        Run the code and a button will show. Click the button and a new window will show. self.action_bilinear is connected to a fake slot. After the window is closed, the memory can not be correctly released. The memory consumption is from the upper image to the middle image before and after closing the window. But if I change self.action_bilinear.triggered.connect(lambda: self.fake_slot('hello')) to self.action_bilinear.triggered.connect(self.fake_slot), the memory consumption is from the upper image to the lower image before and after closing the window, which means the memory is released correctly. Better test with a large image.
        The PySide6 version is 6.5.2 and OS is Windows11.

        JoeCFDJ Offline
        JoeCFDJ Offline
        JoeCFD
        wrote on last edited by
        #3

        @feiyuhuahuo Avoid using lambda to connect signal. Instead use a slot to connect the signal and disconnect them when the widget is cleared. Otherwise, you may get unexpected problems.

        JonBJ 1 Reply Last reply
        0
        • JoeCFDJ JoeCFD

          @feiyuhuahuo Avoid using lambda to connect signal. Instead use a slot to connect the signal and disconnect them when the widget is cleared. Otherwise, you may get unexpected problems.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #4

          @JoeCFD
          But without lambdas it can be hard e.g. to pass parameters.

          @feiyuhuahuo
          I said I think this is expected behaviour from Python, if it's a problem for you you need to work around if it's a deal-breaker.
          Does executing self.action_bilinear.triggered.disconnect() recover the memory? That would disconnect all slots, hopefully including lambdas?

          JoeCFDJ feiyuhuahuoF 2 Replies Last reply
          0
          • JonBJ JonB

            @JoeCFD
            But without lambdas it can be hard e.g. to pass parameters.

            @feiyuhuahuo
            I said I think this is expected behaviour from Python, if it's a problem for you you need to work around if it's a deal-breaker.
            Does executing self.action_bilinear.triggered.disconnect() recover the memory? That would disconnect all slots, hopefully including lambdas?

            JoeCFDJ Offline
            JoeCFDJ Offline
            JoeCFD
            wrote on last edited by
            #5

            @JonB But without lambdas it can be hard e.g. to pass parameters. <== what do you mean? A slot can do the same things as in a lambda func, right?

            JonBJ Pl45m4P 2 Replies Last reply
            0
            • JoeCFDJ JoeCFD

              @JonB But without lambdas it can be hard e.g. to pass parameters. <== what do you mean? A slot can do the same things as in a lambda func, right?

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on last edited by JonB
              #6

              @JoeCFD
              Parameters from the caller (of the connect()), e.g. the common:

              for i in range(10):
                  self.buttons[i].clicked.connect(lambda ii=i: print(ii))
              
              1 Reply Last reply
              0
              • JoeCFDJ JoeCFD

                @JonB But without lambdas it can be hard e.g. to pass parameters. <== what do you mean? A slot can do the same things as in a lambda func, right?

                Pl45m4P Offline
                Pl45m4P Offline
                Pl45m4
                wrote on last edited by
                #7

                @JoeCFD said in Memory can not release correctly because of using lambda as slot:

                what do you mean? A slot can do the same things as in a lambda func, right?

                If types in signal and slot do not match you cant connect or pass directly... with lambda you can modify your received data and process it further.


                If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                ~E. W. Dijkstra

                JoeCFDJ 1 Reply Last reply
                1
                • Pl45m4P Pl45m4

                  @JoeCFD said in Memory can not release correctly because of using lambda as slot:

                  what do you mean? A slot can do the same things as in a lambda func, right?

                  If types in signal and slot do not match you cant connect or pass directly... with lambda you can modify your received data and process it further.

                  JoeCFDJ Offline
                  JoeCFDJ Offline
                  JoeCFD
                  wrote on last edited by
                  #8

                  @Pl45m4 Slot is defined by yourself and you can match them without issues. Using lambda as slot may cause crash when the widget is destroyed. This happened in my app.

                  JonBJ 1 Reply Last reply
                  0
                  • JoeCFDJ JoeCFD

                    @Pl45m4 Slot is defined by yourself and you can match them without issues. Using lambda as slot may cause crash when the widget is destroyed. This happened in my app.

                    JonBJ Offline
                    JonBJ Offline
                    JonB
                    wrote on last edited by JonB
                    #9

                    @JoeCFD said in Memory can not release correctly because of using lambda as slot:

                    Using lambda as slot may cause crash when the widget is destroyed. This happened in my app.

                    You might choose to raise your own topic for this, with example. Ooi, Python (PySide? PyQt?) or C++?

                    JoeCFDJ 1 Reply Last reply
                    0
                    • JonBJ JonB

                      @JoeCFD said in Memory can not release correctly because of using lambda as slot:

                      Using lambda as slot may cause crash when the widget is destroyed. This happened in my app.

                      You might choose to raise your own topic for this, with example. Ooi, Python (PySide? PyQt?) or C++?

                      JoeCFDJ Offline
                      JoeCFDJ Offline
                      JoeCFD
                      wrote on last edited by
                      #10

                      @JonB C++. For example, if the signal comes from another class outside a widget, the signal is connected to a lambda func in the widget. When the widget is destroyed, the lambda may still be attached to that signal. When the signal comes, the lambda tries to process the data of the widget. The app will crash now since the data of the widget does not exist anymore.

                      JonBJ 1 Reply Last reply
                      0
                      • JoeCFDJ JoeCFD

                        @JonB C++. For example, if the signal comes from another class outside a widget, the signal is connected to a lambda func in the widget. When the widget is destroyed, the lambda may still be attached to that signal. When the signal comes, the lambda tries to process the data of the widget. The app will crash now since the data of the widget does not exist anymore.

                        JonBJ Offline
                        JonBJ Offline
                        JonB
                        wrote on last edited by
                        #11

                        @JoeCFD
                        I'd be interested to see your connect() statement. If a QObject is the slot object that should remove it as slot from all signals when destroyed, shouldn't it?

                        MyWidget::MyWidget()
                        {
                            connect(externalObject, &ExternalSignal, this, [] () { });
                        }
                        

                        Doesn't that get disconnected during ~MyWidget()?

                        JoeCFDJ jeremy_kJ 2 Replies Last reply
                        0
                        • JonBJ JonB

                          @JoeCFD
                          I'd be interested to see your connect() statement. If a QObject is the slot object that should remove it as slot from all signals when destroyed, shouldn't it?

                          MyWidget::MyWidget()
                          {
                              connect(externalObject, &ExternalSignal, this, [] () { });
                          }
                          

                          Doesn't that get disconnected during ~MyWidget()?

                          JoeCFDJ Offline
                          JoeCFDJ Offline
                          JoeCFD
                          wrote on last edited by JoeCFD
                          #12

                          @JonB I did not do anything to disconnect in the destructor when lambda was used. I guess the disconnection was not done quickly enough while there are a lot of things going on in the code. Now I do disconnection manually even with slot in ~MyWidget() to avoid crash.

                          1 Reply Last reply
                          0
                          • JonBJ JonB

                            @JoeCFD
                            I'd be interested to see your connect() statement. If a QObject is the slot object that should remove it as slot from all signals when destroyed, shouldn't it?

                            MyWidget::MyWidget()
                            {
                                connect(externalObject, &ExternalSignal, this, [] () { });
                            }
                            

                            Doesn't that get disconnected during ~MyWidget()?

                            jeremy_kJ Offline
                            jeremy_kJ Offline
                            jeremy_k
                            wrote on last edited by
                            #13

                            @JonB said in Memory can not release correctly because of using lambda as slot:

                            @JoeCFD
                            I'd be interested to see your connect() statement. If a QObject is the slot object that should remove it as slot from all signals when destroyed, shouldn't it?

                            MyWidget::MyWidget()
                            {
                                connect(externalObject, &ExternalSignal, this, [] () { });
                            }
                            

                            Doesn't that get disconnected during ~MyWidget()?

                            The problem is that neither PyQt nor PySide support using a context object. This removes two important properties for using lambda functions:

                            • The ability to specify which thread the slot should run in
                            • The ability to automatically disconnect when an indirect receiver is destroyed

                            Asking a question about code? http://eel.is/iso-c++/testcase/

                            JonBJ 1 Reply Last reply
                            1
                            • jeremy_kJ jeremy_k

                              @JonB said in Memory can not release correctly because of using lambda as slot:

                              @JoeCFD
                              I'd be interested to see your connect() statement. If a QObject is the slot object that should remove it as slot from all signals when destroyed, shouldn't it?

                              MyWidget::MyWidget()
                              {
                                  connect(externalObject, &ExternalSignal, this, [] () { });
                              }
                              

                              Doesn't that get disconnected during ~MyWidget()?

                              The problem is that neither PyQt nor PySide support using a context object. This removes two important properties for using lambda functions:

                              • The ability to specify which thread the slot should run in
                              • The ability to automatically disconnect when an indirect receiver is destroyed
                              JonBJ Offline
                              JonBJ Offline
                              JonB
                              wrote on last edited by
                              #14

                              @jeremy_k said in Memory can not release correctly because of using lambda as slot:

                              The problem is that neither PyQt nor PySide support using a context object. This removes two important properties for using lambda functions:

                              I asked whether @JoeCFD was talking about Python or C++ and he said C++.

                              jeremy_kJ 1 Reply Last reply
                              0
                              • JonBJ JonB

                                @jeremy_k said in Memory can not release correctly because of using lambda as slot:

                                The problem is that neither PyQt nor PySide support using a context object. This removes two important properties for using lambda functions:

                                I asked whether @JoeCFD was talking about Python or C++ and he said C++.

                                jeremy_kJ Offline
                                jeremy_kJ Offline
                                jeremy_k
                                wrote on last edited by
                                #15

                                @JonB said in Memory can not release correctly because of using lambda as slot:

                                @jeremy_k said in Memory can not release correctly because of using lambda as slot:

                                The problem is that neither PyQt nor PySide support using a context object. This removes two important properties for using lambda functions:

                                I asked whether @JoeCFD was talking about Python or C++ and he said C++.

                                That's clear. I was being expedient in responding to one of your previous comments.

                                @JonB said in Memory can not release correctly because of using lambda as slot:

                                @JoeCFD
                                But without lambdas it can be hard e.g. to pass parameters.

                                Asking a question about code? http://eel.is/iso-c++/testcase/

                                1 Reply Last reply
                                1
                                • JonBJ JonB

                                  @JoeCFD
                                  But without lambdas it can be hard e.g. to pass parameters.

                                  @feiyuhuahuo
                                  I said I think this is expected behaviour from Python, if it's a problem for you you need to work around if it's a deal-breaker.
                                  Does executing self.action_bilinear.triggered.disconnect() recover the memory? That would disconnect all slots, hopefully including lambdas?

                                  feiyuhuahuoF Offline
                                  feiyuhuahuoF Offline
                                  feiyuhuahuo
                                  wrote on last edited by
                                  #16

                                  @JonB
                                  I add this self.action_bilinear.triggered.disconnect() in closeEvent, but just doesn't work.

                                  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