<div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000"><div>I've found what appears to be a memory leak caused by connecting signals to slots that lack a QtCore.pyqtSlot decorator.  I know that connections without the pyqtSlot decorator are expected to use more memory than connections with the decorator based on https://www.codeproject.com/Articles/1123088/PyQt-signal-slot-connection-performance.  In the script I've pasted below, though, connections without the pyqtSlot decorator continue to consume memory even after the signal has been disconnected and the QObjects have been discarded.  The script does the following:</div><div>  - Instantiates a bunch of SignalObjects, which are QObjects with a signal, and stores the SingalObjects in a list.</div><div>  - Instantiates a bunch of SlotObjects, which are QObjects with slots.  Each SlotObject slot is connected to the signals from all the SignalObjects.  The SlotObjects are then immediately discarded.</div><div>  - Discards all SignalObjects.</div><div>  - Runs gc.collect()</div><div>  - Runs the QApplication's event loop for a second in case there are any pending DeferredDelete events.</div><div><br></div><div>If the SlotObject class uses the QtCore.pyqtSlot decorator, then memory usage at the end of the script is the same as what it was at the beginning of the script, which makes sense since all the objects that get created should be completely destroyed before the script finishes.  Here's the output that I get with pyqtSlot decorators:</div><div>        Memory before signal_objects creation: 17.1MiB</div><div>        Memory before slot objects creation: 17.1MiB</div><div>                Difference: 0.0B</div><div>        Memory after slot objects creation: 17.1MiB</div><div>                Difference: 0.0B</div><div>        Memory after event loop runs: 17.1MiB</div><div>                Difference: 0.0B</div><div>If the SlotObject class doesn't use the QtCore.pyqtSlot decorator, though, then memory usage at the end of the script is substantially higher.  Here's the output that I get without pyqtSlot decorators:</div><div>        Memory before signal_objects creation: 17.0MiB</div><div>        Memory before slot objects creation: 17.0MiB</div><div>                Difference: 0.0B</div><div>        Memory after slot objects creation: 210.6MiB</div><div>                Difference: 193.5MiB</div><div>        Memory after event loop runs: 210.6MiB</div><div>                Difference: 193.5MiB</div><div>Is this expected behavior?  Without pyqtSlot decorators, is there anything I can do to recover the 193 MiB of memory other than terminating the process?  Thanks!</div><div><br></div><div>I've tested the script with Python 3.6.2, PyQt 5.12.2, and Qt 5.12.3 on Windows 10, Linux, and Mac OS.  I've also tested with Python 3.6.5, PyQt 5.13.1, and Qt 5.13.1 on Windows 10.  All of them give similar results.  The script requires the psutil package (https://pypi.org/project/psutil/) to monitor memory usage.</div><div><br></div><div>- Kevin</div><div><br></div><div><br></div><div><div>import gc</div><div>import os</div><div><br></div><div>from PyQt5 import QtCore, QtWidgets</div><div>import psutil</div><div><br></div><div><br></div><div>memory_start = None</div><div><br></div><div><br></div><div>class SignalObject(QtCore.QObject):</div><div><br></div><div>    mySignal = QtCore.pyqtSignal()</div><div><br></div><div><br></div><div>class SlotObject(QtCore.QObject):</div><div><br></div><div>    def __init__(self, signal_objects):</div><div>        super(SlotObject, self).__init__()</div><div><br></div><div>        for cur_signal_obj in signal_objects:</div><div>            cur_signal_obj.mySignal.connect(self.my_slot)</div><div>            cur_signal_obj.mySignal.connect(self.my_slot2)</div><div>            # Immediately disconnecting the signals allows some of the memory to</div><div>            # be recovered after the event loop runs, but not much</div><div>            # cur_signal_obj.mySignal.disconnect(self.my_slot)</div><div>            # cur_signal_obj.mySignal.disconnect(self.my_slot2)</div><div><br></div><div>    # @QtCore.pyqtSlot()</div><div>    def my_slot(self):</div><div>        print("Slot called!")</div><div><br></div><div>    # @QtCore.pyqtSlot()</div><div>    def my_slot2(self):</div><div>        print("Slot2 called!")</div><div><br></div><div><br></div><div>def memory_usage():</div><div>    process = psutil.Process(os.getpid())</div><div>    # with psutils 5.6, uss doesn't work on Windows without elevated permissions</div><div>    # return process.memory_full_info().uss</div><div>    return process.memory_info().vms</div><div><br></div><div><br></div><div>def format_bytes(x):</div><div>    for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:</div><div>        if abs(x) < 1024.0:</div><div>            return "%2.1f%sB" % (x, unit)</div><div>        x /= 1024.0</div><div>    return "%.1fYiB" % x</div><div><br></div><div><br></div><div>def create_slot_objects(signal_objects):</div><div>    for _ in range(1000):</div><div>        SlotObject(signal_objects)</div><div><br></div><div><br></div><div>def report_memory(msg):</div><div>    mem_now = memory_usage()</div><div>    print(f"Memory {msg}: {format_bytes(mem_now)}")</div><div>    print(f"\tDifference: {format_bytes(mem_now - memory_start)}")</div><div><br></div><div><br></div><div>def report_memory_after_event_loop():</div><div>    report_memory("after event loop runs")</div><div><br></div><div><br></div><div>def main():</div><div>    global memory_start</div><div>    app = QtWidgets.QApplication([])</div><div>    QtCore.QTimer.singleShot(1000, report_memory_after_event_loop)</div><div>    QtCore.QTimer.singleShot(1100, app.quit)</div><div>    memory_start = memory_usage()</div><div>    print(f"Memory before signal_objects creation: {format_bytes(memory_start)}")</div><div>    signal_objects = [SignalObject() for _ in range(100)]</div><div>    report_memory("before slot objects creation")</div><div>    create_slot_objects(signal_objects)</div><div>    del signal_objects</div><div>    gc.collect()</div><div>    report_memory("after slot objects creation")</div><div>    app.exec_()</div><div><br></div><div><br></div><div>if __name__ == "__main__":</div><div>    main()</div></div><div><br></div><div class="mb_sig"></div></div>