weakref.finalize: limpieza sin retener objetos

Publicado el: 30/08/2026
Tempo de leitura: 5 minutos
High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.

Recursos externos como archivos temporales, handles nativos, sockets auxiliares y registros de caches deben liberarse. El camino principal debe usar context managers y métodos explícitos, pero algunas bibliotecas necesitan una red de seguridad cuando un objeto es recolectado sin cerrarse. weakref.finalize registra un callback que permanece vivo sin mantener vivo el objeto observado y se ejecuta cuando ese objeto deja de ser alcanzable.

Esta guía explica cómo crear finalizers, evitar referencias fuertes accidentales, ejecutar limpieza anticipada, usar detach(), inspeccionar alive, comprender el cierre del intérprete, probar el comportamiento y decidir por qué la recolección no debe ser el mecanismo principal.

Primer finalizer

import weakref

class Recurso:
    pass

def limpiar(nombre):
    print("limpiando", nombre)

recurso = Recurso()
finalizer = weakref.finalize(recurso, limpiar, "temporal")

Mientras recurso sea alcanzable, el callback no se ejecuta. Cuando deja de serlo, el finalizer puede llamar a limpiar("temporal").

No mantiene vivo el objeto

La asociación usa una referencia débil. Sin embargo, los argumentos del callback se conservan fuertemente. Si uno apunta al objeto observado, puede impedir la recolección.

# Evita esto:
weakref.finalize(recurso, recurso.cerrar)

Un método vinculado mantiene viva su instancia. Prefiere una función independiente con solo los datos externos necesarios:

weakref.finalize(recurso, cerrar_handle, recurso.handle)

Limpieza idempotente

La limpieza debe tolerar peticiones repetidas o coordinar estado para liberar el recurso una sola vez. Un finalizer concreto llama al callback como máximo una vez, pero la clase también puede ofrecer close().

Ejecución anticipada

finalizer()

Llamar al objeto ejecuta inmediatamente el callback si sigue activo y devuelve su resultado. Las llamadas posteriores no repiten la acción.

Propiedad alive

if finalizer.alive:
    finalizer()

alive indica si el callback continúa registrado y no se ha ejecutado ni separado.

detach

detalles = finalizer.detach()

detach() desactiva el finalizer y, si estaba vivo, devuelve una tupla con objeto, función, argumentos y kwargs. Es útil para transferir responsabilidad de limpieza.

peek

detalles = finalizer.peek()

peek() inspecciona el registro sin desactivarlo. La tupla puede mantener temporalmente una referencia fuerte al objeto; no la conserves sin necesidad.

Close explícito con fallback

class ArtefactoTemporal:
    def __init__(self, ruta):
        self.ruta = ruta
        self._finalizer = weakref.finalize(
            self,
            eliminar_archivo,
            ruta,
        )

    def close(self):
        self._finalizer()

El método explícito proporciona liberación determinista. El finalizer queda como respaldo cuando el consumidor olvida cerrar.

Context managers siguen siendo preferibles

with ArtefactoTemporal(ruta) as recurso:
    usar(recurso)

Un context manager garantiza limpieza al terminar el bloque, incluso con excepciones. El momento de la recolección no está garantizado en todas las implementaciones.

Por qué no depender de __del__

__del__ complica herencia, ciclos, excepciones, objetos parcialmente inicializados y shutdown. weakref.finalize separa la función de limpieza y ofrece control por llamada, alive y detach().

El momento no está garantizado

No bases la corrección en cuándo se ejecuta el callback. Referencias pueden permanecer en caches, closures, tracebacks, tasks o threads. Distintas implementaciones de Python recolectan en momentos diferentes.

Basura cíclica

Los finalizers cooperan mejor con el recolector, pero el callback debe evitar resucitar objetos o depender del orden de finalización de un ciclo.

Orden entre recursos

Si varios objetos se vuelven inalcanzables juntos, no supongas una secuencia implícita. Modela propiedad explícitamente para que un manager cierre dependencias antes de sí mismo.

Cierre del intérprete

Finalizers vivos pueden ejecutarse al salir, normalmente en orden inverso de creación. La propiedad atexit controla la participación:

finalizer.atexit = False

Durante shutdown, módulos y globales pueden estar parcialmente desmontados. Pasa funciones independientes y valores concretos al callback.

Excepciones en callbacks

Las excepciones de finalización automática no se propagan normalmente al código que causó la recolección. Mantén callbacks pequeños, captura fallos esperados y registra con seguridad. No uses un finalizer como único lugar para persistir información crítica en red.

Threads

El callback puede ejecutarse en un contexto distinto a la thread creadora. Evita depender de estado thread-local. Si la liberación requiere una thread específica, prográmala explícitamente.

Recursos asyncio

Un finalizer es síncrono y no puede hacer await. No llames una coroutine de cierre directamente. Ofrece async with y aclose(); como máximo, el finalizer puede emitir una advertencia o señalizar un loop activo con mucho cuidado.

Archivos temporales

from pathlib import Path
import weakref

class Artefacto:
    def __init__(self, ruta: Path):
        self.ruta = ruta
        self._cleanup = weakref.finalize(
            self,
            Path.unlink,
            ruta,
            missing_ok=True,
        )

    def eliminar(self):
        self._cleanup()

La función y la ruta no referencian la instancia. missing_ok=True hace idempotente la eliminación si otro camino ya borró el archivo.

Handles nativos

Al integrar bibliotecas C, conserva en los argumentos solo el identificador y una función segura de liberación. Comprueba que la biblioteca nativa siga disponible durante el shutdown.

Objetos retenidos por caches

Si un cache mantiene una referencia fuerte, el finalizer no puede ejecutarse. Considera WeakValueDictionary o una política explícita de eviction.

Pruebas

Las pruebas deben ejercitar primero la limpieza explícita:

finalizer()
assert not finalizer.alive
finalizer()  # no repite

Una prueba separada puede comprobar el fallback, pero evita depender totalmente del momento específico de gc.collect().

Evitar closures que capturan self

# Incorrecto: captura self
weakref.finalize(self, lambda: liberar(self.handle))

Copia el handle a un valor independiente y pásalo a una función de nivel superior o estática.

Transferir propiedad

Cuando el recurso pasa a otro objeto, separa el finalizer antiguo y registra uno nuevo en el nuevo propietario. Así evitas dos componentes liberando el mismo handle.

Observabilidad

El callback de fallback puede incrementar una métrica indicando que faltó cierre explícito. Evita logs ruidosos al salir y no incluyas objetos completos ni identificadores secretos.

Rendimiento

Cada finalizer tiene coste de registro y seguimiento. No crees uno para millones de objetos pequeños cuando un propietario mayor puede gestionar recursos en bloque.

Errores comunes

  • Pasar un método vinculado del objeto: mantiene viva la instancia.
  • Usarlo como camino principal: prefiere close y context managers.
  • Intentar ejecutar una coroutine: el finalizer es síncrono.
  • Depender del orden de callbacks: modela propiedad explícita.
  • Buscar globales durante shutdown: los módulos pueden estar desmontados.
  • Ignorar idempotencia: los caminos explícito y fallback deben coordinarse.

Ejemplo completo: recurso nativo

import weakref

class ConexionNativa:
    def __init__(self, api):
        handle = api.abrir()
        self._api = api
        self._handle = handle
        self._finalizer = weakref.finalize(
            self,
            api.cerrar,
            handle,
        )

    @property
    def cerrada(self):
        return not self._finalizer.alive

    def close(self):
        self._finalizer()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        self.close()

El uso normal es determinista mediante with. Si el consumidor olvida cerrar, el finalizer todavía intenta liberar el handle sin retener la instancia.

Conclusión

weakref.finalize proporciona una red de seguridad controlable para limpieza asociada al ciclo de vida de un objeto. Evita varias debilidades de __del__, pero no convierte la recolección en gestión determinista.

La documentación oficial de weakref.finalize define la API. Usa context managers como camino principal, no captures el objeto observado en el callback y mantén la finalización pequeña, idempotente y segura durante shutdown.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Monitoreo de rendimiento y ejecución de código Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: instrumentación de bajo overhead

    Aprende sys.monitoring en Python para instrumentar ejecución con bajo overhead, eventos selectivos, callbacks y observabilidad segura.

    Ler mais

    Tempo de leitura: 6 minutos
    03/09/2026
    Desarrollador organizando datos con operator.attrgetter en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator.attrgetter: ordena objetos por atributos

    Aprende operator.attrgetter en Python para ordenar, agrupar y transformar objetos por atributos simples o anidados con código claro.

    Ler mais

    Tempo de leitura: 4 minutos
    02/09/2026
    Programación asíncrona con asyncio.Runner en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Runner: reutiliza el event loop con seguridad

    Aprende asyncio.Runner en Python para reutilizar el event loop, controlar contexto, señales, debug, cancelación y cierre asíncrono seguro.

    Ler mais

    Tempo de leitura: 7 minutos
    02/09/2026
    Compresión de datos binarios con Zstandard en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Zstandard con streams y diccionarios

    Aprende compression.zstd en Python para comprimir datos con Zstandard, streaming, diccionarios y límites seguros.

    Ler mais

    Tempo de leitura: 7 minutos
    01/09/2026
    Aplicación Python empaquetada como archivo ejecutable con zipapp
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zipapp en Python: crea apps ejecutables

    Aprende zipapp en Python para empaquetar aplicaciones como archivos pyz ejecutables, incluir dependencias y distribuirlas con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    01/09/2026
    Código Python usado para componer funciones con functools.Placeholder
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    functools.Placeholder: huecos posicionales en partial

    Aprende functools.Placeholder en Python para dejar huecos posicionales en partial y crear APIs funcionales claras y reutilizables.

    Ler mais

    Tempo de leitura: 5 minutos
    31/08/2026