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

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    graphlib en Python: orden topológico

    Aprende graphlib en Python para ordenar dependencias, detectar ciclos, ejecutar tareas listas en paralelo y crear pipelines seguros.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A top view of stacked timber logs showcasing natural textures and patterns.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextlib en Python: gestiona recursos

    Aprende contextlib en Python con contextmanager, ExitStack, suppress, closing, asynccontextmanager y cleanup seguro de recursos.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Close-up of a computer screen displaying colorful programming code with depth of field.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ast en Python: analiza código fuente

    Aprende ast en Python para analizar y transformar código, crear visitors, conservar posiciones, usar literal_eval y evitar riesgos de ejecución.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up of electric plug and socket with vibrant lighting, showcasing technology and energy concepts.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    socket en Python: redes TCP y UDP

    Aprende socket en Python para clientes y servidores TCP y UDP, framing, timeouts, IPv6, concurrencia, TLS y seguridad de red.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    multiprocessing en Python: varios núcleos

    Aprende multiprocessing en Python con procesos, pools, queues, pipes, memoria compartida, cancelación, seguridad y shutdown correcto.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Bright yellow and blue shopping carts arranged in orderly rows outdoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.futures: hilos y procesos en paralelo

    Aprende concurrent.futures en Python con threads, procesos, Future, timeouts, cancelación, backpressure y prevención de deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026