Memory can not release correctly because of using lambda as slot
-
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()


Run the code and a button will show. Click the button and a new window will show.self.action_bilinearis 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 changeself.action_bilinear.triggered.connect(lambda: self.fake_slot('hello'))toself.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. -
F feiyuhuahuo referenced this topic on
-
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()


Run the code and a button will show. Click the button and a new window will show.self.action_bilinearis 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 changeself.action_bilinear.triggered.connect(lambda: self.fake_slot('hello'))toself.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.@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. -
F feiyuhuahuo has marked this topic as solved on
-
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()


Run the code and a button will show. Click the button and a new window will show.self.action_bilinearis 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 changeself.action_bilinear.triggered.connect(lambda: self.fake_slot('hello'))toself.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.@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.
-
@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.
@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 executingself.action_bilinear.triggered.disconnect()recover the memory? That would disconnect all slots, hopefully including lambdas? -
@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 executingself.action_bilinear.triggered.disconnect()recover the memory? That would disconnect all slots, hopefully including lambdas? -
@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?
-
@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?
@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.
-
@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.
-
@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.
@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++?
-
@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++?
@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.
-
@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.
@JoeCFD
I'd be interested to see yourconnect()statement. If aQObjectis 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()? -
@JoeCFD
I'd be interested to see yourconnect()statement. If aQObjectis 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()?@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.
-
@JoeCFD
I'd be interested to see yourconnect()statement. If aQObjectis 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()?@JonB said in Memory can not release correctly because of using lambda as slot:
@JoeCFD
I'd be interested to see yourconnect()statement. If aQObjectis 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
-
@JonB said in Memory can not release correctly because of using lambda as slot:
@JoeCFD
I'd be interested to see yourconnect()statement. If aQObjectis 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
@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_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++.
@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. -
@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 executingself.action_bilinear.triggered.disconnect()recover the memory? That would disconnect all slots, hopefully including lambdas?@JonB
I add thisself.action_bilinear.triggered.disconnect()in closeEvent, but just doesn't work.