假设我有一个对象,并希望在发出PyQt信号时执行其中一个方法。并且假设我希望它使用未通过信号传递的参数来执行此操作。所以我创建了一个lambda作为信号的插槽:
class MyClass(object):
def __init__(self, model):
model.model_changed_signal.connect(lambda: self.set_x(model.x(), silent=True))
现在,通常使用PyQt信号和插槽,信号连接不会阻止垃圾收集。当连接的插槽对象被垃圾收集时,在发出相应的信号时将不再调用插槽。
但是,使用lambdas时这是如何工作的?我没有存储对lambda的引用,但信号槽连接确实保持工作。所以lambda不是垃圾收集。
如果我现在将MyClass
的实例设置为None
,则该实例也不会被垃圾收集:发出model_changed_signal
仍然成功执行lambda。显然,对MyClass
实例的引用保留在某处(可能在lambda的上下文中?) - 这是我不想要的。
为什么会这样?
答案 0 :(得分:2)
示例中的lambda
形成了一个闭包。也就是说,它是一个嵌套函数,它引用其封闭范围内可用的对象。创建闭包的每个函数都为保持引用所需的每个项保留cell object。
在您的示例中,lambda
创建了一个闭包,其中引用了self
方法范围内的本地model
和__init__
变量。如果在某处保留对lambda
的引用,则可以通过其__closure__
属性检查其闭包的所有单元对象。在您的示例中,它将显示如下内容:
>>> print(func.__closure__)
(<cell at 0x7f99c16c5138: MyModel object at 0x7f99bbbf0948>, <cell at 0x7f99c16c5168: MyClass object at 0x7f99bbb81390>)
如果您删除了此处显示的MyModel
和MyClass
对象的所有其他引用,则单元格保留的对象仍会保留。因此,当涉及到对象清理时,您应该始终明确断开连接到可能在相关对象上形成闭包的函数的所有信号。
请注意,在信号/插槽连接方面,PyQt以不同方式处理包装的C ++插槽和Python实例方法。这些类型的可调用类的引用计数在连接到信号时不增加,而lambdas,定义函数,部分对象和静态方法是。这意味着如果删除对后一类可调用的所有其他引用,则任何剩余的信号连接将使它们保持活动状态。如果需要,断开信号将允许这些连接的可调用物被垃圾收集。
上面的一个例外是类方法。 PyQt在创建这些连接时会创建一个特殊的包装器,因此如果删除了对它们的所有其他引用,并且发出了信号,则会引发异常,如下所示:
TypeError: 'managedbuffer' object is not callable
以上内容适用于PyQt5和大多数版本的PyQt4(4.3及更高版本)。