weakref: evita retener objetos en cachés

Publicado el: 27/08/2026
Tempo de leitura: 7 minutos
Close-up view of a computer screen displaying code in a software development environment.

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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código Python asíncrono en un portátil para inspect.markcoroutinefunction
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: detecta wrappers async

    Aprende inspect.markcoroutinefunction en Python para identificar wrappers asíncronos, integrar frameworks y evitar detecciones incorrectas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Código Python para recorrer carpetas y archivos con Path.walk
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: recorre directorios con seguridad

    Aprende Path.walk en Python para recorrer directorios, filtrar archivos, tratar errores y controlar la travesía con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Depuración de un proceso Python en terminal con código
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: depura procesos Python en ejecución

    Aprende a conectar pdb a un proceso Python en ejecución, inspeccionar la pila y diagnosticar bloqueos de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Código Python para representar fracciones exactas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: convierte números en fracciones

    Aprende fractions.from_number en Python para convertir números en fracciones exactas, controlar precisión, validar entradas y evitar redondeos inesperados.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026