El módulo weakref permite crear referencias que apuntan a un objeto sin impedir que el garbage collector lo elimine. Una referencia normal mantiene vivo el objeto mientras exista al menos un vínculo fuerte. Una referencia débil puede consultarse mientras el objeto existe, pero no aumenta su ownership ni prolonga artificialmente su ciclo de vida.
Este recurso resulta útil en caches, registros de objetos, mapas de metadatos, observers, árboles de componentes y estructuras que no deben ser propietarias de los elementos almacenados. Las referencias débiles también ayudan a reducir retención accidental, pero no sustituyen un modelo claro de ownership. El diseño debe definir cuándo puede desaparecer un objeto y cómo responde el código.
Referencias fuertes y débiles
Una variable normal crea una referencia fuerte.
objeto = MiClase()
alias = objeto
Mientras exista objeto o alias, la instancia permanece accesible. Con weakref.ref(), la referencia puede quedar vacía cuando desaparecen todos los vínculos fuertes.
import weakref
objeto = MiClase()
referencia = weakref.ref(objeto)
print(referencia() is objeto)
del objeto
print(referencia())
La llamada referencia() devuelve el objeto vivo o None después de la recolección.
No todos los objetos admiten weakref
Las instancias de clases definidas por el usuario normalmente permiten referencias débiles. Muchos tipos built-in, como list y dict, no las aceptan directamente, aunque ciertas subclases pueden hacerlo.
import weakref
try:
weakref.ref([])
except TypeError as error:
print(error)
Una API genérica debe manejar TypeError o documentar que los objetos proporcionados necesitan soporte para weak references.
Clases con __slots__
Al usar __slots__, incluye "__weakref__" para permitir referencias débiles.
class Usuario:
__slots__ = ("nombre", "__weakref__")
def __init__(self, nombre):
self.nombre = nombre
Sin ese slot especial, weakref.ref(instancia) lanza TypeError. Es una decisión del layout de la clase y conviene tomarla antes de publicar la API.
Evita la carrera entre comprobación y uso
No compruebes una referencia y después la llames de nuevo. En código concurrente, el objeto puede desaparecer entre ambas operaciones.
objeto = referencia()
if objeto is not None:
objeto.ejecutar()
La variable local crea una referencia fuerte temporal mientras el objeto se utiliza.
Callbacks de la referencia
weakref.ref() puede recibir un callback invocado cuando el objeto referenciado está a punto de desaparecer.
import weakref
def eliminado(referencia):
print("objeto recolectado")
objeto = MiClase()
referencia = weakref.ref(objeto, eliminado)
El callback recibe la propia referencia débil, no el objeto original. En ese momento normalmente ya no puede recuperarse el target.
No captures el objeto en el callback
Una closure que mantiene una referencia fuerte al target anula el propósito de weakref.
def crear_callback(objeto):
def callback(referencia):
print(objeto)
return callback
Este patrón mantiene vivo el objeto. Captura solo identificadores, strings, contadores o metadatos independientes.
weakref.proxy
weakref.proxy() crea un proxy que reenvía operaciones al objeto sin exigir la llamada ().
import weakref
objeto = MiClase()
proxy = weakref.proxy(objeto)
proxy.ejecutar()
Si el objeto ya fue recolectado, el proxy lanza ReferenceError. Úsalo cuando la sintaxis transparente realmente mejore la API; ref() hace más explícita la posible ausencia.
Proxies de objetos callable
Funciones e instancias con __call__ pueden producir un CallableProxyType. El proxy sigue sin poseer el target.
No guardes un proxy en un lugar que exija disponibilidad garantizada. ReferenceError debe tratarse como resultado normal del lifecycle.
WeakValueDictionary
WeakValueDictionary conserva claves fuertes y valores débiles. Cuando ya no existen referencias fuertes a un valor, la entrada desaparece automáticamente.
import weakref
cache = weakref.WeakValueDictionary()
objeto = MiClase()
cache["principal"] = objeto
print("principal" in cache)
del objeto
print("principal" in cache)
Es útil para interning, factories, identity maps y caches que no deben retener resultados para siempre.
Un cache débil no garantiza retención
Una entrada puede desaparecer inmediatamente si ningún caller conserva una referencia fuerte.
cache[clave] = construir()
Si construir() devuelve un objeto sin otro owner, el valor puede desaparecer al terminar la instrucción. El caller que lo necesite debe guardarlo en una variable fuerte.
WeakKeyDictionary
WeakKeyDictionary almacena claves débiles y valores fuertes. Cuando una clave desaparece, la asociación se elimina.
import weakref
metadatos = weakref.WeakKeyDictionary()
usuario = Usuario("Ana")
metadatos[usuario] = {"visitas": 1}
Es una forma práctica de asociar información auxiliar a objetos sin modificar sus clases.
Identidad, igualdad y claves
Objetos distintos que comparan como iguales pueden producir comportamientos sorprendentes en mappings débiles. Hash e igualdad gobiernan el diccionario, mientras la eliminación depende del ciclo de vida de una clave concreta.
Prefiere objetos con identidad e igualdad estables. No modifiques campos usados por __hash__ después de insertar.
WeakSet
WeakSet almacena objetos débilmente y elimina miembros recolectados.
import weakref
observadores = weakref.WeakSet()
observadores.add(listener)
for observador in list(observadores):
observador.actualizar()
Crear un snapshot con list() puede ser útil cuando los callbacks modifican el conjunto durante la iteración.
Observers sin fugas
Un sistema de eventos que mantiene listeners en una lista normal puede conservar pantallas, controllers o componentes. Un WeakSet reduce ese riesgo para listeners basados en objetos.
El diseño todavía debe definir el caso sin listeners y el registro de funciones, métodos y closures.
WeakMethod
Un bound method es temporal porque cada acceso a objeto.metodo crea un nuevo objeto de método. WeakMethod guarda una referencia débil recuperable al par instancia-función.
import weakref
referencia = weakref.WeakMethod(objeto.procesar)
metodo = referencia()
if metodo is not None:
metodo()
Este patrón es especialmente útil en registradores de callbacks orientados a objetos.
finalize
weakref.finalize() registra una función ejecutada cuando un objeto es recolectado.
import weakref
recurso = Recurso()
finalizador = weakref.finalize(recurso, cerrar_handle, recurso.handle)
El finalizador permanece vivo hasta ejecutarse o cancelarse. Suele ser más cómodo y robusto que un callback directo de ref().
La finalización no debe ser el cleanup principal
La liberación determinística debe usar with, close() u otra API explícita. La recolección puede ocurrir tarde, en un orden inesperado o durante el shutdown del intérprete.
Usa finalize como red de seguridad, no para transacciones, commits, flushes críticos o deadlines.
alive, detach y ejecución explícita
Un finalizador posee la propiedad alive. detach() elimina y devuelve la información registrada sin ejecutarla. Llamar directamente al finalizador lo ejecuta una sola vez.
if finalizador.alive:
finalizador()
Esto permite cleanup explícito y evita duplicidad.
Orden de finalización
Durante el shutdown, los finalizadores restantes pueden ejecutarse según reglas de la implementación, frecuentemente en orden inverso a su creación. No construyas dependencias complejas alrededor de ese comportamiento.
Un finalizador no debería depender de módulos globales que quizá ya estén parcialmente desmontados. Pasa directamente la función y valores simples.
Ciclos de referencia
Las referencias débiles pueden expresar enlaces no propietarios en un grafo. Un hijo puede guardar un vínculo débil al padre cuando el padre ya posee al hijo fuertemente.
El garbage collector moderno resuelve muchos ciclos, pero weakref sigue siendo útil para expresar ownership y evitar retención por registros externos.
El momento de recolección varía
En CPython, el conteo de referencias libera muchos objetos rápidamente. Otras implementaciones pueden recolectar más tarde. Incluso en CPython, los ciclos dependen del colector cíclico.
No escribas lógica de producción que exija un callback justo después de del. En tests controlados puede usarse gc.collect(), pero no debe ser requisito de corrección.
Threads y sincronización
Los containers débiles no vuelven atómicas las operaciones compuestas. Un target puede desaparecer entre observaciones y un container puede cambiar durante la iteración.
Usa locks cuando varias threads actualicen el mismo registro. Resuelve la referencia una sola vez y conserva la variable local durante toda la operación.
Tasks asíncronas
Las tasks de background cuya ejecución deba continuar necesitan referencias fuertes según la API del event loop. Un container débil no es un supervisor fiable.
Guarda tasks en un set fuerte y elimínalas mediante callback cuando concluyan.
Rendimiento y complejidad
Las referencias débiles añaden objetos auxiliares, callbacks y mantenimiento de containers. También hacen el lifecycle menos obvio para quien lee el código.
Úsalas cuando resuelvan un problema real de ownership o cache. Una lista normal con deregistro explícito puede ser más simple y rápida.
Herramientas de diagnóstico
weakref.getweakrefcount(objeto) devuelve la cantidad de referencias débiles y proxies. getweakrefs() devuelve los objetos correspondientes.
print(weakref.getweakrefcount(objeto))
print(weakref.getweakrefs(objeto))
Son útiles en diagnóstico, aunque el estado puede cambiar inmediatamente en código concurrente.
Estrategia de tests
Prueba el objeto vivo, la recolección tras eliminar la última referencia fuerte, callback ejecutado una vez, finalización explícita, cancelación, objetos no compatibles, clases con slots, concurrencia y entradas de cache que desaparecen antes de lo esperado.
Evita assertions sobre el orden global de recolección entre objetos independientes.
Errores comunes
Los fallos frecuentes son usar un cache débil cuando la retención es necesaria, capturar el target en el callback, reutilizar un proxy tras la recolección, olvidar __weakref__ en slots, esperar garbage collection inmediata, usar finalizadores para commits críticos y no conservar referencias fuertes a callbacks o tasks que deben seguir vivos.
Conclusión
weakref modela relaciones no propietarias y permite caches que liberan entradas automáticamente cuando los objetos dejan de usarse. Usa ref para acceso explícito, proxies para sintaxis transparente, diccionarios y sets débiles para registros y finalize como protección adicional.
Mantén explícito el ownership principal, acepta que el target puede desaparecer y prefiere context managers para liberación determinística. Consulta la documentación oficial de weakref y contextlib en Python.







