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

    Código Python en un portátil para crear dataclasses dinámicas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    make_dataclass decorator: transforma dataclasses

    Aprende a usar el parámetro decorator de make_dataclass para validar, registrar y transformar dataclasses dinámicas en Python.

    Ler mais

    Tempo de leitura: 6 minutos
    23/10/2026
    Estructura de código y XML para canonicalización con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ElementTree canonicalize: normaliza XML en Python

    Aprende ElementTree canonicalize en Python para comparar XML, generar hashes estables y controlar namespaces, espacios y comentarios.

    Ler mais

    Tempo de leitura: 5 minutos
    22/10/2026
    Código Python en una pantalla
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    T-strings y templatelib: interpolación estructurada

    Aprende t-strings y string.templatelib en Python para interpolación estructurada, renderizado seguro, logs y procesamiento personalizado.

    Ler mais

    Tempo de leitura: 6 minutos
    22/10/2026
    Base SQLite y código Python para exportación selectiva con iterdump
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    SQLite iterdump: crea dumps selectivos en Python

    Aprende sqlite3.Connection.iterdump con filtro para crear dumps SQLite selectivos, validar dependencias y restaurar con seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    21/10/2026
    Código y archivos que representan compresión de datos con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Zstandard rápido en Python

    Aprende compression.zstd en Python para comprimir archivos y datos con velocidad, streaming, integridad y seguridad.

    Ler mais

    Tempo de leitura: 7 minutos
    21/10/2026
    Archivo de configuración y estructura de código para ConfigParser en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ConfigParser: lee INI sin sección en Python

    Aprende ConfigParser sin sección en Python con validación, tipos, compatibilidad, capas de configuración y escritura segura.

    Ler mais

    Tempo de leitura: 5 minutos
    20/10/2026