Cделаю отступление, и напомню, что Qt изначально писался на С++ и для С++, будучи ориентированный под его специфику. (В С++ контроля и освобождения памяти, есть только некоторые приёмы обеспечивающие очистку памяти. В Python есть сборщик мусора, достаточно простой, но удалением объектов и освобождением занимается именно он. Так же доступ к объектам происходит по указателям - они в создаются в куче.)
глядя на код наподобии:
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.parent = parent
self.initUI()
Тут мы можем получить ещё и кольцевые ссылки на уровне Python, но это другая история.
(К слову, если всё-таки требуется родительский элемент, стоит воспользоваться вызовом self.parent() .)
Да и просто во вроде бы безобидном классе:
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.initUI()
def initUI(self):
self.ok_button = QPushButton("OK", self)
layout = QVBoxLayout(self)
layout.addWidget(self.ok_button)
self.show()
У меня были сомнения. Всё-таки стык между Python и С++. И на мой взгляд нужно быть хорошим знатоком одновременно Python, С++ и Qt, чтобы быть уверенным в происходящем.
Удивительно, но материалов в сети оказалось сравнительно мало:
http://stackoverflow.com/questions/37918012/pyqt-give-parent-when-creating-a-widget
Почти одно и то же, но на разных языках:
https://habrahabr.ru/post/210304/
http://enki-editor.org/2014/08/23/Pyqt_mem_mgmt.html
Проверить, остались ли в памяти виджеты (на сколько я понимаю, и С++ и Python сторон) можно,
добавив после вызова app.exec_() строку:
print('\n'.join(repr(w) for w in app.allWidgets()))
Один из способов избежать утечки памяти, переписать код так, чтобы ссылки хранились на стороне Python классов.
def openDialog(self):
self.dialog = MyDialog()
self.dialog.show()
Ещё один момент:
Если мы пишем свой класс, наследуясь от QWidget (или любых классов, унаследованных в свою очередь от QWidget), то безопаснее использовать атрибут WA_DeleteOnClose, если его можно будет установить.
Это обеспечит удалении класса на стороне С++, даже если обёртка на Python останется.
(В этом случае может возникнуть проблема, если остались не отсоединённые сигналы и слоты к С++ части класса.)
Для PyQt4:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Пример:
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Полный пример теста со stackoverflow, переписанный мной для PyQt5:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QDialog,
QWidget, QPushButton, QVBoxLayout)
from PyQt5.QtCore import Qt
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.initUI()
def initUI(self):
self.ok_button = QPushButton("OK", self)
layout = QVBoxLayout(self)
layout.addWidget(self.ok_button)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
parent = QWidget()
obj = MyDialog(parent)
app.exec_()
print('\n'.join(repr(w) for w in app.allWidgets()))
У меня показывает:
<PyQt5.QtWidgets.QDesktopWidget object at 0x000001F41CE94438>
<PyQt5.QtWidgets.QWidget object at 0x000001F41CE944C8>
<PyQt5.QtWidgets.QWidget object at 0x000001F41CE94288>
Что похоже на правду, поскольку parent и obj ещё в области видимости (scope) - она у них глобальная для скрипта (модуля).
Но obj остался только как обёртка - при попытке вызвать функцию из С++ сгенерировалась ошибка.
Если не писать self.setAttribute(Qt.WA_DeleteOnClose), то строк (а соответственно оставшихся в памяти объектов) будет больше, и они будут включать в себя QPushButton и всё остальное.
Несколько проверенных примеров просто с наследованием от QWidget или QMainWindow и созданием кнопок и прочих виджетов с указанием parent (а так же частичным хранением ссылок на них) показали утечку без WA_DeleteOnClose.
Итог:
Логичным выглядит стараться использовать self.setAttribute(Qt.WA_DeleteOnClose) в конструкторе при создании своих виджетов, главных окон и диалогов.
(В этом случае может возникнуть проблема, если остались неотсоединённые сигналы и слоты к С++ части класса.)
глядя на код наподобии:
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.parent = parent
self.initUI()
Тут мы можем получить ещё и кольцевые ссылки на уровне Python, но это другая история.
(К слову, если всё-таки требуется родительский элемент, стоит воспользоваться вызовом self.parent() .)
Да и просто во вроде бы безобидном классе:
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.initUI()
def initUI(self):
self.ok_button = QPushButton("OK", self)
layout = QVBoxLayout(self)
layout.addWidget(self.ok_button)
self.show()
У меня были сомнения. Всё-таки стык между Python и С++. И на мой взгляд нужно быть хорошим знатоком одновременно Python, С++ и Qt, чтобы быть уверенным в происходящем.
Удивительно, но материалов в сети оказалось сравнительно мало:
http://stackoverflow.com/questions/37918012/pyqt-give-parent-when-creating-a-widget
Почти одно и то же, но на разных языках:
https://habrahabr.ru/post/210304/
http://enki-editor.org/2014/08/23/Pyqt_mem_mgmt.html
Проверить, остались ли в памяти виджеты (на сколько я понимаю, и С++ и Python сторон) можно,
добавив после вызова app.exec_() строку:
print('\n'.join(repr(w) for w in app.allWidgets()))
Один из способов избежать утечки памяти, переписать код так, чтобы ссылки хранились на стороне Python классов.
def openDialog(self):
self.dialog = MyDialog()
self.dialog.show()
Ещё один момент:
Если мы пишем свой класс, наследуясь от QWidget (или любых классов, унаследованных в свою очередь от QWidget), то безопаснее использовать атрибут WA_DeleteOnClose, если его можно будет установить.
Это обеспечит удалении класса на стороне С++, даже если обёртка на Python останется.
(В этом случае может возникнуть проблема, если остались не отсоединённые сигналы и слоты к С++ части класса.)
Для PyQt4:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Пример:
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Полный пример теста со stackoverflow, переписанный мной для PyQt5:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QDialog,
QWidget, QPushButton, QVBoxLayout)
from PyQt5.QtCore import Qt
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.initUI()
def initUI(self):
self.ok_button = QPushButton("OK", self)
layout = QVBoxLayout(self)
layout.addWidget(self.ok_button)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
parent = QWidget()
obj = MyDialog(parent)
app.exec_()
print('\n'.join(repr(w) for w in app.allWidgets()))
У меня показывает:
<PyQt5.QtWidgets.QDesktopWidget object at 0x000001F41CE94438>
<PyQt5.QtWidgets.QWidget object at 0x000001F41CE944C8>
<PyQt5.QtWidgets.QWidget object at 0x000001F41CE94288>
Что похоже на правду, поскольку parent и obj ещё в области видимости (scope) - она у них глобальная для скрипта (модуля).
Но obj остался только как обёртка - при попытке вызвать функцию из С++ сгенерировалась ошибка.
Если не писать self.setAttribute(Qt.WA_DeleteOnClose), то строк (а соответственно оставшихся в памяти объектов) будет больше, и они будут включать в себя QPushButton и всё остальное.
Несколько проверенных примеров просто с наследованием от QWidget или QMainWindow и созданием кнопок и прочих виджетов с указанием parent (а так же частичным хранением ссылок на них) показали утечку без WA_DeleteOnClose.
Итог:
Логичным выглядит стараться использовать self.setAttribute(Qt.WA_DeleteOnClose) в конструкторе при создании своих виджетов, главных окон и диалогов.
(В этом случае может возникнуть проблема, если остались неотсоединённые сигналы и слоты к С++ части класса.)
Комментариев нет:
Отправить комментарий