Mover arrays numpy de VBA a Python y viceversa

Tengo un script VBA en Microsoft Access. El script VBA es parte de un proyecto grande con varias personas, por lo que no es posible abandonar el entorno VBA.

En una sección de mi script, necesito hacer un álgebra lineal complicada en una tabla rápidamente. Por lo tanto, muevo las tablas VBA escritas como conjuntos de registros ) a Python para hacer álgebra lineal, y de vuelta a VBA. Las matrices en python se representan como matrices numpy .

Parte del álgebra lineal es propietaria y, por lo tanto, estamos comstackndo las secuencias de comandos propietarias con pyinstaller .

Los detalles del proceso son los siguientes:

  1. La secuencia de comandos VBA crea un archivo csv que representa la tabla input.csv .
  2. El script VBA ejecuta el script python a través de la línea de comando
  3. La secuencia de comandos de python carga el archivo csv input.csv como una matriz numpy , hace un álgebra lineal en él y crea un archivo de salida csv output.csv .
  4. VBA espera hasta que output.csv Python, luego carga output.csv .
  5. VBA elimina el archivo input.csv ya no se necesita y el archivo output.csv .

Este proceso es ineficiente.

¿Hay alguna forma de cargar matrices VBA en Python (y viceversa) sin el desorden csv? ¿Estos métodos funcionan con el código comstackdo de Python a través de pyinstaller?

He encontrado los siguientes ejemplos en stackoverflow que son relevantes. Sin embargo, no abordan mi problema específicamente.

Resultado de retorno de Python a Vba

Cómo pasar una variable de Python a VBA Sub

Solución 1

Recupere la instancia de Access en ejecución de COM y obtenga / establezca los datos directamente con el script de python a través de la API de COM:

VBA:

 Private Cache Public Function GetData() GetData = Cache Cache = Empty End Function Public Sub SetData(data) Cache = data End Sub Sub Usage() Dim wshell Set wshell = VBA.CreateObject("WScript.Shell") ' Make the data available via GetData()' Cache = Array(4, 6, 8, 9) ' Launch the python script compiled with pylauncher ' Debug.Assert 0 = wshell.Run("C:\dev\myapp.exe", 0, True) ' Handle the returned data ' Debug.Assert Cache(3) = 2 End Sub 

Python ( myapp.exe ):

 import win32com.client if __name__ == "__main__": # get the running instance of Access app = win32com.client.GetObject(Class="Access.Application") # get some data from Access data = app.run("GetData") # return some data to Access app.run("SetData", [1, 2, 3, 4]) 

Solucion 2

O cree un servidor COM para exponer algunas funciones a Access:

VBA :

 Sub Usage() Dim Py As Object Set Py = CreateObject("Python.MyModule") Dim result result = Py.MyFunction(Array(5, 6, 7, 8)) End Sub 

Python ( myserver.exe o myserver.py ):

 import sys, os, win32api, win32com.server.localserver, win32com.server.register class MyModule(object): _reg_clsid_ = "{5B4A4174-EE23-4B70-99F9-E57958CFE3DF}" _reg_desc_ = "My Python COM Server" _reg_progid_ = "Python.MyModule" _public_methods_ = ['MyFunction'] def MyFunction(self, data) : return [(1,2), (3, 4)] def register(*classes) : regsz = lambda key, val: win32api.RegSetValue(-2147483647, key, 1, val) isPy = not sys.argv[0].lower().endswith('.exe') python_path = isPy and win32com.server.register._find_localserver_exe(1) server_path = isPy and win32com.server.register._find_localserver_module() for cls in classes : if isPy : file_path = sys.modules[cls.__module__].__file__ class_name = '%s.%s' % (os.path.splitext(os.path.basename(file_path))[0], cls.__name__) command = '"%s" "%s" %s' % (python_path, server_path, cls._reg_clsid_) else : file_path = sys.argv[0] class_name = '%s.%s' % (cls.__module__, cls.__name__) command = '"%s" %s' % (file_path, cls._reg_clsid_) regsz("SOFTWARE\\Classes\\" + cls._reg_progid_ + '\\CLSID', cls._reg_clsid_) regsz("SOFTWARE\\Classes\\AppID\\" + cls._reg_clsid_, cls._reg_progid_) regsz("SOFTWARE\\Classes\\CLSID\\" + cls._reg_clsid_, cls._reg_desc_) regsz("SOFTWARE\\Classes\\CLSID\\" + cls._reg_clsid_ + '\\LocalServer32', command) regsz("SOFTWARE\\Classes\\CLSID\\" + cls._reg_clsid_ + '\\ProgID', cls._reg_progid_) regsz("SOFTWARE\\Classes\\CLSID\\" + cls._reg_clsid_ + '\\PythonCOM', class_name) regsz("SOFTWARE\\Classes\\CLSID\\" + cls._reg_clsid_ + '\\PythonCOMPath', os.path.dirname(file_path)) regsz("SOFTWARE\\Classes\\CLSID\\" + cls._reg_clsid_ + '\\Debugging', "0") print('Registered ' + cls._reg_progid_) if __name__ == "__main__": if len(sys.argv) > 1 : win32com.server.localserver.serve(set([v for v in sys.argv if v[0] == '{'])) else : register(MyModule) 

Tenga en cuenta que tendrá que ejecutar el script una vez sin ningún argumento para registrar la clase y para que esté disponible para VBA.CreateObject .

Ambas soluciones funcionan con pylauncher y la matriz recibida en python se puede convertir con numpy.array(data) .

Dependencia:

https://pypi.python.org/pypi/pywin32

Puede intentar cargar su conjunto de registros en una matriz, atenuada como Doble

 Dim arr(1 to 100, 1 to 100) as Double 

haciendo un bucle, luego pase el puntero al primer elemento ptr = VarPtr (arr (1, 1)) a Python, donde

arr = numpy.ctypeslib.as_array(ptr, (100 * 100,)) ?

Pero VBA todavía será el propietario de la memoria de matriz