La gestión automática de memoria es una de las mayores ventajas de Python, pero las referencias normales pueden mantener objetos vivos durante más tiempo del necesario. Una caché, un registro de observadores o un diccionario de metadatos puede retener objetos grandes aunque el resto de la aplicación ya no los use. El módulo weakref en Python ofrece referencias débiles: enlaces que permiten acceder a un objeto mientras existe sin impedir que el recolector de basura recupere su memoria.
En esta guía aprenderás la diferencia entre referencias fuertes y débiles, cómo usar weakref.ref(), WeakValueDictionary, WeakKeyDictionary, WeakSet, WeakMethod, proxies y finalize(). El tema complementa los artículos sobre descriptors en Python, optimización de scripts lentos, el módulo collections, generadores y evaluación perezosa y yield y generadores eficientes.
Referencias fuertes y ciclo de vida
Una variable común mantiene una referencia fuerte. Mientras exista al menos una referencia fuerte, el objeto continúa vivo. Las variables locales, los atributos, los elementos de listas y los valores de diccionarios crean habitualmente este tipo de referencia.
class Imagen:
def __init__(self, nombre):
self.nombre = nombre
imagen = Imagen("portada.png")
cache = {"portada": imagen}
del imagen
print(cache["portada"].nombre)Eliminar la variable local no destruye el objeto porque el diccionario todavía lo conserva. Este comportamiento suele ser correcto, pero puede ser indeseado cuando el mapeo solo funciona como caché opcional o índice auxiliar. Una referencia débil no aumenta la cantidad de referencias fuertes. Cuando solo quedan referencias débiles, el objeto puede ser recolectado.
Crear una referencia con weakref.ref
weakref.ref() devuelve un objeto invocable. Al llamarlo, recibes el referente si todavía está vivo o None si ya fue eliminado.
import weakref
class Documento:
pass
documento = Documento()
referencia = weakref.ref(documento)
print(referencia() is documento)
del documento
print(referencia())La documentación oficial de weakref denomina referente al objeto señalado por la referencia débil. La propiedad esencial es que la referencia débil no prolonga la vida del referente.
La forma segura de recuperar el objeto
No compruebes una referencia y después la llames de nuevo. En código concurrente, otro hilo podría eliminar la última referencia fuerte entre ambas operaciones. Recupera el objeto una sola vez y conserva esa referencia local durante el uso.
objeto = referencia()
if objeto is None:
print("El objeto ya no existe")
else:
objeto.procesar()Al asignar el resultado a objeto, el programa crea temporalmente una referencia fuerte. Este patrón evita que el referente desaparezca en medio de la operación.
Callbacks al eliminar el objeto
weakref.ref() acepta un callback que se ejecuta cuando el referente está a punto de finalizar. El callback recibe la referencia débil, no el objeto que ya no está disponible.
import weakref
class Sesion:
pass
def eliminada(referencia):
print("Sesión eliminada del índice")
sesion = Sesion()
referencia = weakref.ref(sesion, eliminada)
del sesionNo captures el objeto vigilado mediante closure, argumento o método vinculado. Esa referencia fuerte indirecta impediría la recolección. Las excepciones lanzadas por el callback se escriben en la salida de error, pero no pueden propagarse al código que liberó la última referencia fuerte.
Cachés automáticas con WeakValueDictionary
WeakValueDictionary mantiene claves normales y valores débiles. Cuando un valor deja de tener referencias fuertes en otras partes de la aplicación, su entrada desaparece automáticamente.
from weakref import WeakValueDictionary
class Perfil:
def __init__(self, identificador):
self.identificador = identificador
_cache = WeakValueDictionary()
def cargar_perfil(identificador):
perfil = _cache.get(identificador)
if perfil is None:
perfil = Perfil(identificador)
_cache[identificador] = perfil
return perfil
perfil = cargar_perfil(42)
print(list(_cache))
del perfil
print(list(_cache))Este patrón es útil para objetos costosos que pueden reconstruirse: imágenes decodificadas, modelos temporales, wrappers de recursos y representaciones intermedias. La caché es una optimización, no almacenamiento persistente, porque una entrada puede desaparecer cuando ningún consumidor mantiene el objeto.
Cuándo una caché débil no basta
Una caché débil puede vaciarse enseguida si los consumidores no conservan los valores. Tampoco define tamaño máximo, política LRU, caducidad ni consistencia distribuida. functools.lru_cache() puede ser mejor para resultados pequeños y deterministas. Una base de datos o caché externa es apropiada cuando los datos deben sobrevivir a la recolección o al reinicio del proceso.
Elige WeakValueDictionary cuando la regla sea: reutiliza el objeto mientras alguna parte del programa lo necesite realmente, pero no lo mantengas vivo únicamente porque aparece en la caché.
Metadatos externos con WeakKeyDictionary
WeakKeyDictionary mantiene las claves de forma débil. Permite asociar información con objetos de terceros sin modificar esos objetos y sin asumir la propiedad de su ciclo de vida.
from weakref import WeakKeyDictionary
class Conexion:
pass
metricas = WeakKeyDictionary()
conexion = Conexion()
metricas[conexion] = {"consultas": 3}
print(metricas[conexion])
del conexion
print(len(metricas))Este recurso resulta útil en instrumentación, descriptors, frameworks, adaptadores y plugins. Cuando la instancia principal desaparece, sus metadatos auxiliares también se eliminan.
Igualdad e identidad en claves débiles
Existe un detalle importante cuando dos objetos distintos se consideran iguales. Insertar la segunda clave puede reemplazar el valor sin sustituir la identidad de la primera clave mantenida internamente. Cuando la clave original se recolecta, la entrada puede desaparecer aunque el segundo objeto igual siga vivo.
Si son habituales las instancias distintas pero iguales, elimina la entrada anterior antes de asignar la nueva o usa un identificador inmutable. Las clases que personalizan igualdad y hash deben incluir pruebas específicas para este comportamiento.
Registros de observadores con WeakSet
WeakSet implementa la interfaz de conjunto manteniendo elementos débiles. Es una opción natural para observadores, listeners y registros de objetos activos.
from weakref import WeakSet
class Observador:
def actualizar(self, evento):
print("Evento:", evento)
observadores = WeakSet()
observador = Observador()
observadores.add(observador)
for elemento in list(observadores):
elemento.actualizar("datos modificados")
del observador
print(len(observadores))Iterar sobre una copia puede ser útil cuando los callbacks modifican el registro. Un conjunto débil evita que el publicador se convierta accidentalmente en propietario de todos los suscriptores.
Métodos vinculados y WeakMethod
Un método vinculado como instancia.actualizar es un objeto temporal creado al acceder al atributo. Una referencia débil normal puede caducar inmediatamente. WeakMethod conserva la información necesaria para reconstruir el método mientras existan la instancia y la función original.
from weakref import WeakMethod
class Vista:
def actualizar(self):
print("Actualizando")
vista = Vista()
callback = WeakMethod(vista.actualizar)
metodo = callback()
if metodo is not None:
metodo()
del vista
print(callback())Este patrón es especialmente útil en sistemas de eventos, interfaces gráficas y buses de mensajes que no deben retener consumidores indefinidamente.
Proxies débiles
weakref.proxy() crea un proxy que reenvía la mayoría de las operaciones al referente. Evita llamar explícitamente a la referencia, pero después de la recolección cualquier acceso produce ReferenceError.
import weakref
class Configuracion:
entorno = "producción"
configuracion = Configuracion()
proxy = weakref.proxy(configuracion)
print(proxy.entorno)
del configuracion
try:
print(proxy.entorno)
except ReferenceError:
print("Configuración no disponible")Los proxies nunca son hashable, aunque el referente sí lo sea. En APIs de biblioteca, las referencias explícitas suelen hacer más visible el estado ausente.
Limpieza con weakref.finalize
weakref.finalize() registra una función de limpieza que se ejecuta como máximo una vez cuando el objeto es recolectado. El finalizador se mantiene vivo automáticamente, lo que simplifica su gestión frente a un callback de bajo nivel.
import shutil
import tempfile
import weakref
class DirectorioTemporal:
def __init__(self):
self.ruta = tempfile.mkdtemp()
self._finalizador = weakref.finalize(
self,
shutil.rmtree,
self.ruta,
ignore_errors=True,
)
def eliminar(self):
self._finalizador()
@property
def eliminado(self):
return not self._finalizador.aliveEl finalizador puede invocarse explícitamente y las llamadas posteriores no repiten la limpieza. Por defecto, los finalizadores vivos también se ejecutan durante el cierre normal del intérprete en orden inverso a su creación.
No retengas el objeto vigilado
La función y los argumentos pasados a finalize() no deben mantener el objeto vigilado directa ni indirectamente. Pasar un método vinculado de la propia instancia es un error frecuente.
# Evita:
# weakref.finalize(self, self.cerrar)
# Prefiere una función externa con los datos necesarios:
weakref.finalize(self, cerrar_recurso, identificador)Si el finalizador conserva self, la instancia posee el finalizador, el finalizador posee el método y el método posee la instancia. El objeto puede no llegar a ser recolectable.
Tipos compatibles con referencias débiles
Las instancias de clases Python normales suelen admitir referencias débiles. Las funciones Python, métodos, conjuntos, generadores, sockets, arrays y otros tipos también son compatibles. Las listas y los diccionarios integrados no lo son directamente.
import weakref
class MiLista(list):
pass
valores = MiLista([1, 2, 3])
referencia = weakref.ref(valores)
print(referencia())Las subclases de list y dict pueden añadir soporte, pero algunos tipos como tuple e int siguen sin admitir referencias débiles incluso al heredarlos.
weakref y __slots__
Declarar __slots__ desactiva el soporte automático para referencias débiles, salvo que la secuencia incluya "__weakref__".
import weakref
class Usuario:
__slots__ = ("nombre", "__weakref__")
def __init__(self, nombre):
self.nombre = nombre
usuario = Usuario("Ana")
referencia = weakref.ref(usuario)
print(referencia().nombre)Este detalle importa en clases con muchas instancias que utilizan slots para reducir memoria y también participan en cachés o registros débiles.
Recolección de basura y pruebas
En CPython, los objetos sin ciclos suelen destruirse cuando desaparece la última referencia fuerte. Otras implementaciones pueden recolectarlos más tarde. La documentación del módulo estándar gc explica controles y herramientas de diagnóstico.
import gc
import weakref
class Elemento:
pass
elemento = Elemento()
referencia = weakref.ref(elemento)
del elemento
gc.collect()
assert referencia() is NoneUsa gc.collect() en pruebas controladas e investigaciones, no como solución rutinaria para una propiedad de objetos poco clara.
Referencias débiles y ciclos
El recolector cíclico de Python ya resuelve muchos ciclos. Las referencias débiles ayudan cuando una relación no representa propiedad. Por ejemplo, un hijo puede referirse débilmente al padre cuando el padre ya posee fuertemente al hijo. El diseño deja claro qué objeto controla la vida.
No sustituyas todas las relaciones por referencias débiles. Algún componente debe poseer el objeto mientras sea necesario. De lo contrario, los valores pueden desaparecer demasiado pronto y causar fallos intermitentes.
Consideraciones de concurrencia
Una referencia débil puede quedar muerta después de eliminarse la última referencia fuerte. El patrón seguro consiste en llamarla una sola vez y usar el objeto local devuelto. Comprobar la vida con una llamada y recuperar con otra crea una condición de carrera.
Los contenedores débiles tampoco convierten automáticamente operaciones compuestas en atómicas. Si varios hilos actualizan una caché o registro, protege la secuencia completa de lectura, modificación y escritura.
Errores frecuentes
- Usar una caché débil como almacenamiento persistente.
- Mantener solo referencias débiles y esperar que los objetos continúen vivos.
- Llamar dos veces a la referencia en código concurrente.
- Capturar el referente dentro de su callback o finalizador.
- Pasar el método vinculado del propio objeto a
finalize(). - Olvidar
"__weakref__"en una clase con slots. - Esperar soporte directo en listas, diccionarios, enteros o tuplas.
- Ignorar igualdad personalizada en
WeakKeyDictionary. - Suponer recolección inmediata en todas las implementaciones Python.
Buenas prácticas
- Usa referencias débiles solo en relaciones que no representan propiedad.
- Prefiere contenedores débiles y
finalize()a callbacks de bajo nivel. - Recupera el referente una sola vez antes de usarlo.
- Mantén funciones de limpieza externas e independientes de la instancia.
- Documenta que las entradas de una caché débil pueden desaparecer.
- Prueba el comportamiento después de eliminar referencias fuertes.
- Mide memoria antes y después de adoptar weak references.
- Añade límites, caducidad y sincronización cuando sean necesarios.
Conclusión
weakref en Python permite observar, indexar y reutilizar objetos sin asumir la responsabilidad de mantenerlos vivos. Las referencias básicas ofrecen control de bajo nivel, mientras WeakValueDictionary, WeakKeyDictionary y WeakSet resuelven cachés, metadatos y registros de observadores. WeakMethod trabaja con callbacks vinculados y finalize() proporciona limpieza de una sola ejecución.
El módulo funciona mejor cuando la propiedad es explícita. Las referencias fuertes deben conservar los objetos por necesidades reales de la aplicación; las referencias débiles solo ofrecen acceso auxiliar. Con callbacks sin ciclos, soporte correcto para slots, recuperación segura y pruebas del ciclo de vida, weakref ayuda a reducir retenciones accidentales sin volver impredecible el comportamiento.







