Python y Excel 365: el operador @ en fórmulas exportadas con openpyxl (solución con xlwings)

Problema frecuente para quienes automatizan Excel con Python: al exportar fórmulas usando openpyxl, Excel 365 añade el operador @ delante de las funciones (por ejemplo, @SI en vez de SI). Esto ocurre porque openpyxl no tiene soporte nativo para matrices dinámicas, y Excel 365 interpreta las fórmulas en modo clásico aplicando la intersección implícita (el operador @ fuerza que solo se muestre el primer resultado).

La raíz técnica es la diferencia entre Formula (modo clásico, que openpyxl usa) y Formula2 (modo moderno con matrices dinámicas y desbordamiento natural). A partir de Excel 365, las fórmulas que podrían desbordar se limitan con @ si se escriben en modo clásico.

La comunidad propuso usar xlwings, que permite acceder a la API COM de Excel y escribir fórmulas con Formula2. Se compartió un script Python completo y bien documentado que detecta si el archivo ya está abierto en Excel (para no abrir una instancia duplicada), escribe fórmulas usando Formula2 con fallback a Formula clásico, y gestiona correctamente el ciclo de vida de la instancia Excel:

``python
Con xlwings, usar Formula2 para modo moderno (matrices dinámicas)
try:
rng.api.Formula2 = formula # Modo moderno
except Exception:
rng.api.Formula = formula # Fallback al modo clásico
`

El trade-off es rendimiento: openpyxl procesa en ~30 segundos (trabaja directamente con el XML del xlsx), mientras que xlwings tarda ~4-5 minutos (usa COM, necesita una instancia de Excel corriendo). Pero la ventaja de xlwings es que las fórmulas matriciales y dinámicas funcionan correctamente sin el operador @.

Funciones y conceptos clave: Formula2 vs Formula (API COM), operador de intersección implícita (@), matrices dinámicas y desbordamiento (spill), SEQUENCE, SUM, TAKE con referencias desbordadas (F1#`).

El problema: Excel te mete un arroba que tú no escribiste

Automatizas la generación de un Excel con Python, escribes las fórmulas con openpyxl, abres el fichero... y te encuentras con que Excel ha metido un arroba delante de tus funciones. Donde tú pusiste una función condicional, ahora hay un arroba y luego la función. Y las fórmulas que deberían derramar devuelven un único valor.

No es un bug de openpyxl ni un capricho de Excel. Es el operador de intersección implícita, y entender por qué aparece es lo que te permite quitarlo de en medio.

Por qué pasa: Formula frente a Formula2

Excel guarda las fórmulas de dos maneras distintas, y la diferencia es histórica.

  • El modo clásico es el de toda la vida, anterior a las matrices dinámicas. En ese modo, una fórmula ocupa una celda y devuelve un valor. Si la expresión pudiera generar varios resultados, Excel aplica la intersección implícita y se queda con el que corresponde a la fila actual.
  • El modo moderno es el que llegó con las matrices dinámicas. Aquí una fórmula puede devolver una matriz entera y derramarse por las celdas de al lado sin pedir permiso.

openpyxl trabaja directamente sobre el XML del fichero xlsx y escribe en el atributo del modo clásico, porque no tiene soporte nativo para matrices dinámicas. Cuando Excel 365 abre ese fichero y ve una fórmula en modo clásico que podría derramar, hace lo que le dicta la compatibilidad: le pone el arroba delante para forzar que devuelva un solo valor.

Dicho de otro modo: el arroba no es un error, es Excel siendo retrocompatible con un fichero que le llega marcado como antiguo.

La solución: xlwings y la API COM

La comunidad apuntó al mismo sitio: si el problema es que openpyxl solo puede escribir en el atributo clásico, hay que escribir en el moderno. Y para eso hace falta hablar con Excel de verdad, no con el XML.

xlwings hace exactamente eso: se conecta a la API COM de Excel, lo que da acceso a la propiedad del modo moderno. El patrón es tan simple como esto:

# Intenta el modo moderno (matrices dinámicas)
try:
    rng.api.Formula2 = formula
except Exception:
    rng.api.Formula = formula   # Fallback al modo clásico

El try con except no es paranoia: en versiones antiguas de Excel la propiedad moderna no existe, y ahí el fallback al modo clásico salva la ejecución.

En el grupo se compartió un script completo y bien documentado que va más allá del snippet y resuelve los detalles feos de trabajar con COM:

  • Detecta si el fichero ya está abierto en Excel, para engancharse a esa instancia en lugar de abrir una duplicada. Si no lo haces, acabas con dos Excel peleándose por el mismo archivo.
  • Escribe con la propiedad moderna y cae a la clásica si no está disponible.
  • Gestiona el ciclo de vida de la instancia: cerrar lo que se abrió, dejar abierto lo que ya estaba. Un proceso de Excel huérfano en segundo plano bloquea el fichero y no te enteras hasta el siguiente intento.

El precio: rendimiento

Esto no sale gratis, y conviene saberlo antes de migrar todo el pipeline.

  • openpyxl: unos 30 segundos. Trabaja directamente sobre el XML comprimido del xlsx, sin abrir Excel.
  • xlwings: unos 4 o 5 minutos. Necesita una instancia real de Excel corriendo y cada escritura viaja por COM.

Es un orden de magnitud de diferencia. La decisión práctica suele ser mixta: usa openpyxl para el grueso de los datos y las fórmulas sencillas que nunca van a derramar, y reserva xlwings para el puñado de celdas donde de verdad necesitas matrices dinámicas.

Conceptos clave

  • Intersección implícita (el operador arroba) — fuerza que una expresión devuelva un solo valor en lugar de una matriz. Si aparece sin que lo escribas, es señal de que la fórmula está guardada en modo clásico.
  • Modo clásico frente a modo moderno — dos formas de guardar la misma fórmula en el fichero. openpyxl solo escribe la primera; xlwings puede escribir la segunda.
  • Derramamiento — el comportamiento por el que una fórmula ocupa las celdas contiguas necesarias para mostrar toda su matriz.
  • Referencias a rangos derramados — el sufijo de almohadilla que apunta a toda la matriz generada por otra celda, sin tener que saber su tamaño.
  • API COM — la interfaz que permite controlar Excel como aplicación desde otro programa, y la única vía para tocar propiedades que el XML no expone.

Conclusión

El arroba fantasma es de esos problemas que parecen un fallo de la librería y resultan ser una diferencia de formato de fichero. Una vez sabes que existen dos maneras de guardar una fórmula, la solución se elige sola: si tu automatización genera fórmulas que deben derramar, necesitas escribir en modo moderno, y eso hoy pasa por xlwings.

Si tu caso es el contrario, fórmulas simples de una sola celda, openpyxl sigue siendo la opción sensata por velocidad. Casos como este salen a menudo en la comunidad de InflueXcel, donde la parte de Excel y la de Python se cruzan más de lo que parece.

Más contenido de Excel en InflueXcel