Tecnología · GUÍA PRÁCTICA
¿cached_property se ejecuta dos veces en Python 3.12? Añade single-flight solo si hace falta
Python 3.12 quitó el bloqueo antiguo de cached_property. Demuestra primero que la duplicación importa y guarda el resultado correcto bajo un bloqueo por instancia.

Que la caché quede coherente no significa que el getter se ejecute una sola vez. Desde Python 3.12, dos hilos pueden entrar en la primera lectura de la misma instancia antes de que cualquiera guarde el valor.
No añadas un bloqueo por reflejo. Un cálculo puro e idempotente sobre datos inmutables suele duplicar solo el coste. Si cobra, crea archivos, asigna identificadores externos, cambia estado compartido o depende de identidad, trata la ejecución única como requisito independiente.
1. Demuestra la carrera antes de cambiar el diseño
Sincroniza dos hilos con una barrera para la primera lectura de la misma instancia. Incrementa un contador al entrar y espera brevemente. En Python 3.12 o posterior, dos ejecuciones están permitidas aunque ambos lectores terminen con el mismo valor.
Anota la versión, que comparten instancia y si el marco sustituye cached_property. Crear dos instancias demuestra otro problema.
2. Clasifica por consecuencia, no solo por coste
Un cálculo puro sobre campos inmutables suele ser idempotente; repetirlo desperdicia tiempo. Unificar trabajo caro es una decisión de rendimiento.
Inserciones, pagos, archivos, solicitudes, contadores y registros afectan a la corrección. Usa también claves idempotentes o transacciones cuando sea posible; un bloqueo local no garantiza exactamente una vez entre procesos.
3. Guarda el valor bajo el bloqueo de la instancia
Usa property normal, un centinela privado, un campo de caché y un bloqueo por objeto. La ruta rápida devuelve; la lenta bloquea, vuelve a comprobar, calcula, asigna y libera.
Bloquear solo dentro del getter de cached_property y dejar que el decorador escriba después de liberar puede mantener una pequeña ventana de carrera.
4. Conserva la semántica de fallo e invalidación
Asigna solo tras terminar con éxito. Una excepción deja el centinela para que el siguiente acceso reintente. No publiques un objeto mutable a medio construir.
Invalida bajo el mismo bloqueo y decide si lectores en curso pueden usar el valor anterior. No uses None como centinela si es un resultado válido.
5. No recrees la contención de Python 3.11
Un bloqueo en el descriptor o la clase serializa instancias independientes y repite el problema eliminado. Crear un bloqueo nuevo en cada acceso tampoco protege nada.
Si un bloqueo por objeto cuesta demasiado, considera construcción anticipada, precomputación inmutable, una caché que fusione solicitudes o rediseñar el efecto lateral.
6. Acepta el arreglo con cuatro pruebas
Comprueba una ejecución con dos lecturas concurrentes de una instancia; solapamiento entre dos instancias; reintento tras el primer fallo; y un solo valor nuevo después de invalidar bajo el bloqueo.
Ejecuta en todas las versiones compatibles. Verifica el contrato de tu aplicación, no una implementación histórica no documentada.
