Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime of object in lambda connected to pyqtSignal

Suppose I have an object and want one of its methods to be executed when a PyQt signal is emitted. And suppose I want it to do so with a parameter that is not passed by the signal. So I create a lambda as the signal's slot:

class MyClass(object):
    def __init__(self, model):
        model.model_changed_signal.connect(lambda: self.set_x(model.x(), silent=True))

Now, normally with PyQt signals and slots, signal connections don't prevent garbage collection. When a connected slot's object is garbage collected, the slot will no longer be called when the corresponding signal is emitted.

However, how does this work when using lambdas? I don't store a reference to the lambda, yet the signal-slot connection does keep working. So the lambda is not garbage collected.

If I now set the instance of MyClass to None, that instance is not garbage collected either: emitting the model_changed_signal still executes the lambda succesfully. So apparently, a reference to the instance of MyClass is kept around somewhere (maybe in the context of the lambda?) - which I don't want.

Why does this happen?

like image 399
tjalling Avatar asked Dec 22 '17 12:12

tjalling


1 Answers

The lambda in your example forms a closure. That is, it is a nested function that references objects available in its enclosing scope. Every function that creates a closure keeps a cell object for every item it needs to maintain a reference to.

In your example, the lambda creates a closure with references to the local self and model variables inside the scope of the __init__ method. If you keep a reference to the lambda somewhere, you can examine all the cell objects of its closure via its __closure__ attribute. In your example, it would display something like this:

>>> print(func.__closure__)
(<cell at 0x7f99c16c5138: MyModel object at 0x7f99bbbf0948>, <cell at 0x7f99c16c5168: MyClass object at 0x7f99bbb81390>)

If you deleted all other references to the MyModel and MyClass objects shown here, the ones kept by the cells would still remain. So when it comes to object cleanup, you should always explicitly disconnect all signals connected to functions that may form closures over the relevant objects.


Note that when it comes to signal/slot connections, PyQt treats wrapped C++ slots and Python instance methods differently. The reference counts of these types of callable are not increased when they are connected to signals, whereas lambdas, defined functions, partial objects and static methods are. This means that if all other references to the latter types of callable are deleted, any remaining signal connections will keep them alive. Disconnecting the signals will allow such connected callables to be garbage-collected, if necessary.

The one exception to the above is class methods. PyQt creates a special wrapper when creating connections to these, so if all other references to them are deleted, and the signal is emitted, an exception will be raised, like this:

TypeError: 'managedbuffer' object is not callable

The above should apply to PyQt5 and most versions of PyQt4 (4.3 and greater).

like image 171
ekhumoro Avatar answered Nov 15 '22 06:11

ekhumoro