В моем приложении PyQt5 (Python 3.8) многие QWidget()
создаются и уничтожаются динамически. Хотя на уничтожение каждого QWidget()
обращаю пристальное внимание - наверняка что-то проскочило. С каждым циклом потребление оперативной памяти увеличивается.
1. Один лайнер, чтобы отключить их все
Я прочитал следующее сообщение в блоге об отключении pyqtSignal()
s: https://www.sep.com/blog/prevent-signal-slot-memory-leaks-in-python/
Внизу сообщения в блоге упоминается следующая однострочная строка для отключения всех pyqtSignal()
от/от (?) QObject()
:
for x in filter(lambda y: type(y) == pyqtBoundSignal and 0 < element.receivers(y), map(lambda z: getattr(element, z), dir(element))): x.disconnect()
Предположим, что element
представляет экземпляр QObject()
, интересно, уничтожит ли этот однострочный код все входящие или исходящие сигналы?
Я попытался переписать эту однострочную функцию в реальной функции:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
def disconnect_signals_connected_to_qobject(qobj:QObject) -> None:
'''
Disconnect all signals that are connected to a slot in the given 'qobj'.
'''
for x in filter(
lambda y: type(y) == pyqtBoundSignal and 0 < qobj.receivers(y),
map(
lambda z: getattr(qobj, z),
dir(qobj)
)
):
x.disconnect()
return
Если код уничтожает все входящие сигналы, то выбранное имя disconnect_signals_connected_to_qobject()
правильное. В противном случае я должен переименовать функцию в disconnect_signals_emitted_from_qobject()
. Какое правильное имя?
2. Действительно ли это отключает *все* pyqtSignals?
У меня все еще есть это щемящее чувство, что код забудет несколько разъединений. Некоторое время назад я нашел следующий фрагмент кода, в котором утверждается, что он полностью отключает сигнал:
def discon_sig(signal):
'''
Disconnect only breaks one connection at a time,
so loop to be safe.
'''
while True:
try:
signal.disconnect()
except TypeError:
break
return
Похоже, что однострочник из сообщения в блоге забыл тот факт, что вызов signal.disconnect()
отключает только одно соединение за раз. Поэтому, возможно, мне следует переписать свою функцию следующим образом:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
def disconnect_signals_connected_to_qobject(qobj:QObject) -> None:
'''
Disconnect all signals that are connected to a slot in the given 'qobj'.
'''
# Define inner function for a complete signal
# disconnection
def discon_sig(signal):
while True:
try:
signal.disconnect()
except TypeError:
break
return
# Loop to find all signals that need
# disconnection
for x in filter(
lambda y: type(y) == pyqtBoundSignal and 0 < qobj.receivers(y),
map(
lambda z: getattr(qobj, z),
dir(qobj)
)
):
discon_sig(x)
return
3. Достаточно ли отключения?
Я стал еще больше беспокоиться после прочтения этой переписки между пользователем и создателем PyQt Филом Томпсоном:
https://www.riverbankcomputing.com/pipermail/pyqt/2019-September/042180.html
Короче говоря, пользователь утверждает, что отключения pyqtSignal()
s недостаточно для предотвращения утечек памяти. Согласно его экспериментам, крайне важно, чтобы каждый pyqtSignal()
был привязан к украшенному слоту. Бросьте в несколько неукрашенных слотов, и вас ждет вечная гибель:
Кевин («пользователь» пишет Филу Томпсону)
Мое предположение заключалось в том, что использование памяти сигнальными соединениями не будет накапливаться (т. е. что использование памяти не будет зависеть от количества созданных экземпляров
SignalObject()
), что, по-видимому, и происходит с@pyqtSlot()
оформленной версией. Вcreate_slot_objects()
создается экземпляр каждогоSlotObject()
, который соединяет сигналы, а затем должен быть немедленно собран мусор, что должно разъединить сигналы. В версии скрипта без декораторов@pyqtSlot()
число, сообщаемое для Память после создания объектов слота, пропорционально количеству созданных экземпляровSlotObject()
, поэтому я предположил, что произошла утечка памяти, но я может, конечно, продумывать вещи неправильно.
Я не смог найти четкого ответа от Фила, поэтому теперь я официально параноик по поводу всех без исключения pyqtSignal()
, живущих в моем приложении.
4. Является ли отключение *всех* сигналов действительно правильным?
Я только что провел тест своего приложения. При просмотре виджетов моего QGridLayout()
(в обратном порядке) я делаю следующее, чтобы очистить их:
disconnect_signals_connected_to_qobject(widg)
widg.setParent(None)
widg.deleteLater()
sip.delete(widg)
Несмотря на это, у меня была утечка памяти почти 100 MB
за цикл, причем один цикл заключался в заполнении моей таблицы 1000 строками и повторной очистке таблицы.
Теперь я опускаю отключение pyqtSignal
:
widg.setParent(None)
widg.deleteLater()
sip.delete(widg)
Утечка памяти снизилась примерно до 20~25 MB
за цикл. Как это возможно? Может быть, неизбирательное отключение всех pyqtSignal
препятствует надлежащей очистке задействованных QWidget()
? Может быть, нужно тщательно решить, какие pyqtSignal
оставить, а какие отключить? Означает ли это, что сообщение в блоге, процитированное в начале этого вопроса (см. https://www.sep.com/blog/prevent-signal-slot-memory-leaks-in-python/) на самом деле дает плохой совет?
signal.disconnect([slot])
Если [слот] опущен, сигнал отключается от всего, к чему он подключен. Можете ли вы предоставить MRE? - person musicamante   schedule 29.01.2021QStyle()
к меню — то, что мы привыкли делать с некоторыми настроеннымиQWidget()
, — казалось, мешало правильной сборке мусора. Мы не знаем, почему именно — может быть, это пища для дополнительных исследований или другой вопрос. Спасибо @ekhumoro за разъяснение stackoverflow.com/ вопросы/21586643/ - person K.Mulier   schedule 30.01.2021