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

    Vivid close-up of a python resting among autumn leaves, showcasing its intricate patterns.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextlib.aclosing: cierra generadores async

    Aprende contextlib.aclosing en Python para cerrar generadores async tras break, return, excepciones, cancelación y consumo parcial.

    Ler mais

    Tempo de leitura: 5 minutos
    30/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

    SimpleNamespace: objetos ligeros con atributos

    Aprende SimpleNamespace en Python para crear objetos ligeros por atributos, convertir diccionarios, copiar y elegir modelos tipados.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Close-up of a detailed South America map showcasing geography and cartography.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ChainMap en Python: mapas por capas

    Aprende ChainMap en Python para combinar configuración y scopes por capas, controlar precedencia, escrituras y snapshots seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    29/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

    itertools.pairwise: analiza pares consecutivos

    Aprende itertools.pairwise en Python para analizar pares consecutivos, calcular deltas, detectar transiciones, huecos y errores de orden.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    itertools.batched: procesa iterables por lotes

    Aprende itertools.batched en Python para procesar iterables por lotes, controlar memoria, usar strict y crear pipelines resilientes.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    cmp_to_key: adapta comparadores antiguos a sorted

    Aprende cmp_to_key en Python para adaptar comparadores antiguos, ordenar con locale, conservar estabilidad y evitar relaciones incoherentes.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026