Vida útil del objeto en Lambda conectado a pyqtSignal

Supongamos que tengo un objeto y quiero que uno de sus métodos se ejecute cuando se emite una señal PyQt. Y supongamos que quiero que lo haga con un parámetro que no pasa la señal. Así que creo un lambda como ranura de la señal:

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

Ahora, normalmente con las señales y ranuras de PyQt, las conexiones de señal no evitan la recolección de basura. Cuando el objeto de una ranura conectada se recolecta como basura, la ranura ya no se llamará cuando se emita la señal correspondiente.

Sin embargo, ¿cómo funciona esto cuando se utilizan las lambdas? No almaceno una referencia a la lambda, sin embargo, la conexión de la ranura de señal sigue funcionando. Entonces la lambda no es basura recolectada.

Si ahora configuro la instancia de MyClass en None , esa instancia tampoco se recolecta como basura: la emisión del model_changed_signal aún ejecuta la lambda con éxito. Así que aparentemente, una referencia a la instancia de MyClass se guarda en algún lugar (tal vez en el contexto de la lambda?) – que no quiero.

¿Por qué pasó esto?

La lambda en su ejemplo forma un cierre. Es decir, es una función anidada que hace referencia a los objetos disponibles en su ámbito adjunto. Cada función que crea un cierre mantiene un objeto de celda para cada elemento al que necesita mantener una referencia.

En su ejemplo, la lambda crea un cierre con referencias a las variables locales del model y del self dentro del scope del método __init__ . Si mantiene una referencia a la lambda algún lugar, puede examinar todos los objetos de la celda de su cierre a través de su atributo __closure__ . En tu ejemplo, mostraría algo como esto:

 >>> print(func.__closure__) (, ) 

Si elimina todas las demás referencias a los objetos MyModel y MyClass que se muestran aquí, las que conservan las celdas permanecerán. Entonces, cuando se trata de la limpieza de objetos, siempre debe desconectar explícitamente todas las señales conectadas a funciones que puedan formar cierres sobre los objetos relevantes.


Tenga en cuenta que cuando se trata de conexiones de señal / ranura, PyQt trata las ranuras C ++ envueltas y los métodos de instancia de Python de manera diferente. Los recuentos de referencia de estos tipos de llamadas no se incrementan cuando están conectados a señales, mientras que las lambdas, las funciones definidas, los objetos parciales y los métodos estáticos sí lo son. Esto significa que si se eliminan todas las demás referencias a los últimos tipos de llamadas, las conexiones de señal restantes las mantendrán activas. La desconexión de las señales permitirá que dichos callables conectados sean recolectados en la basura, si es necesario.

La única excepción a lo anterior son los métodos de clase. PyQt crea un envoltorio especial cuando crea conexiones a estos, por lo que si se eliminan todas las demás referencias a ellos y se emite la señal, se generará una excepción, como esta:

 TypeError: 'managedbuffer' object is not callable 

Lo anterior debe aplicarse a PyQt5 y a la mayoría de las versiones de PyQt4 (4.3 y superiores).