连接到 pyqtSignal 的 lambda 中对象的生命周期

Lifetime of object in lambda connected to pyqtSignal

假设我有一个对象并希望在发出 PyQt 信号时执行其方法之一。假设我希望它使用信号未传递的参数来执行此操作。所以我创建了一个 lambda 作为信号槽:

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

现在,通常使用 PyQt 信号和槽,信号连接不会阻止垃圾收集。当一个连接槽的对象被垃圾回收时,当相应的信号被发射时,槽将不再被调用。

但是,这在使用 lambda 时如何工作?我没有存储对 lambda 的引用,但信号槽连接确实继续工作。所以 lambda 没有被垃圾收集。

如果我现在将 MyClass 的实例设置为 None,该实例也不会被垃圾回收:发出 model_changed_signal 仍然会成功执行 lambda。很明显,对 MyClass 实例的引用保存在某个地方(也许在 lambda 的上下文中?)——我不想要。

为什么会这样?

您示例中的 lambda 形成了一个闭包。也就是说,它是一个嵌套函数,引用其封闭范围内可用的对象。每个创建闭包的函数都为它需要维护引用的每个项目保留一个 cell object

在您的示例中,lambda 创建了一个闭包,其中引用了 __init__ 方法范围内的局部 selfmodel 变量。如果您在某处保留对 lambda 的引用,则可以通过其 __closure__ 属性检查其闭包的所有单元格对象。在您的示例中,它会显示如下内容:

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

如果您删除了对此处显示的 MyModelMyClass 对象的所有其他引用,单元格保留的对象仍将保留。因此,当涉及到对象清理时,您应该始终显式断开连接到可能对相关对象形成闭包的函数的所有信号。


请注意,当涉及到 signal/slot 连接时,PyQt 以不同方式处理包装的 C++ 槽和 Python 实例方法。这些类型的可调用对象的引用计数在连接到信号时 不会 增加,而 lambda、已定义函数、部分对象和静态方法会增加。这意味着如果删除对后一种可调用类型的所有其他引用,任何剩余的信号连接将使它们保持活动状态。如有必要,断开信号将允许对此类连接的可调用对象进行垃圾收集。

上述的一个例外是 class 方法。 PyQt 在创建与它们的连接时会创建一个特殊的包装器,因此如果删除所有其他对它们的引用并发出信号,则会引发异常,如下所示:

TypeError: 'managedbuffer' object is not callable

以上应该适用于 PyQt5 和大多数版本的 PyQt4(4.3 及更高版本)。