Como eu (quase) matei o VBA do Excel com o Python
O pessoal do mercado financeiro ama Excel, mas aposto que todo mundo odeia ter que lidar com VBA.
Aqui na empresa não adianta, não consegui convencer que Web é melhor que Excel para ferramentas complexas. Aqueles que desenvolvem ferramentas querem continuar usando apenas Excel e Python, porém, até então, não existe uma maneira fácil de integrar essas duas ferramentas, precisamos passar raiva com VBA de maneira má escrita e macros aterrorizantes.
Então eu reuni todo o ódio que eu tenho por VBA, eu decidi me juntar a ele, para matá-lo de dentro pra fora...
Desenvolvi um proof of concept, aqui está ele:
Video no Youtube
www.youtube.com/watch?v=Be4ytQViWY0
VBA
São basicamente 3 linhas! (depois de desenvolver a biblioteca para tornar isso possível)
Primeiro é definido o que eu chamei de "headers", que é um dicionário contendo a chave sendo como o dado vai transitar pela API (Python) e o valor que seria o nome da coluna para humanos (Excel).
Dim headers As Object
Sub init()
Set headers = Dict( _
"a", "Variável A", _
"b", "Variável B", _
"result", "Resultado" _
)
End Sub
Sub generate_input()
init
DataView.fetch "/demo/generate_input", , headers
End Sub
Sub calculate()
init
Set input_data = DataView.from_table("input", List("a", "b"), headers)
DataView.fetch "/demo/multiply", input_data, headers
End Sub
Python (FastAPI)
Ultimamente ando usando o FastAPI, ele já resolve muitas coisas de graça! Junto com ele estou usando o pandera, que é uma lib para validação de dataframes do pandas, e de brinde ele nos entrega uma class muito parecida com o BaseModel do pydantic.
Imports
from fastapi import APIRouter
import numpy
import pandas as pd
import pandera as pa
from common import DataView, NumberFormats
from pandera.typing import DataFrame, Series
A única coisa diferente aqui é a importação do DataView, que é uma classe que eu criei para facilitar na comunicação com a lib no VBA
Definição dos schemas
router = APIRouter(prefix="/demo", tags=["Demo"])
class Input(pa.DataFrameModel):
a: Series[float] = pa.Field(coerce=True)
b: Series[float] = pa.Field(coerce=True)
class Output(pa.DataFrameModel):
result: Series[float] = pa.Field(coerce=True)
Definição das funções
@router.post("/generate_input")
def generate_input():
df = pd.DataFrame(dict(a=numpy.random.rand(10) * 10, b=numpy.random.rand(10)))
return DataView(
df,
schema=Input,
table_name="input", # Uma tabela no Excel deve ter esse mesmo nome
formatters=dict(
a=NumberFormats.INTEGER,
b=NumberFormats.DECIMAL_2,
),
)
@router.post("/multiply")
def multiply(df: DataFrame[Input]):
df["result"] = df["a"] * df["b"]
return DataView(
df,
schema=Output,
table_name="output", # Uma tabela no Excel deve ter esse mesmo nome
formatters=dict(
result=NumberFormats.PERCENT_2,
),
)
It just works
E simples assim, seguindo a vida mais feliz, cortando um belo pedaço de uma linguagem datada.
Finalização
O que acharam dessa gambiarra maravilhosa? Acham que seria útil para vocês? Eu fiz o certo? Comentem seus pensamentos. Obrigado!