2017-07-14 65 views
6

我在Microsoft Access中有一个VBA脚本。 VBA脚本是一个包含多个人的大型项目的一部分,因此无法离开VBA环境。将numpy数组从VBA移动到Python并返回

在我的脚本的一部分中,我需要快速在桌面上执行复杂的线性代数。所以,我把编写为recordsets)的VBA表移动到Python中去做线性代数,然后回到VBA中。 Python中的矩阵表示为numpy数组。

一些线性代数是专有的,所以我们用pyinstaller编译专有脚本。是

过程的细节如下:

  1. 的VBA脚本创建表示表input.csv csv文件。
  2. 的VBA脚本运行通过命令行
  3. - Python脚本加载csv文件input.csv作为numpy矩阵的Python脚本,在这日线性代数,并且创建输出csv文件output.csv
  4. VBA等待python完成,然后加载output.csv
  5. VBA删除不再需要的input.csv文件和output.csv文件。

该过程效率低下。

有没有办法将VBA矩阵加载到Python(而不是csv混乱)?这些方法是否可以通过pyinstaller与编译好的python代码一起工作?

我在相关的计算器上找到了以下示例。但是,它们没有具体解决我的问题。

Return result from Python to Vba

How to pass Variable from Python to VBA Sub

+0

如果VBA脚本存在只是为了在表中创建CSV,把它传递到Python,得到的Python回来,并删除剩菜,那么它看起来像有无需从VBA运行代码。你知道你可以[使用python连接MS Access数据库](https://stackoverflow.com/questions/853370/what-do-i-need-to-read-microsoft-access-databases-using-python)使用[ PYODBC](https://pypi.python.org/pypi/pyodbc/)/ [PYPYODBC](https://pypi.python.org/pypi/pypyodbc)?如果您仍然需要VBA来实现您的目标,请解释一下。 – Tehscript

回答

5

解决方案1 ​​

要么检索访问的COM运行实例和获取/通过COM API使用Python脚本直接设置数据:

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 

的Pythonmyapp.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]) 

解决方案2

或者创建一个COM服务器暴露了一些函数来访问:

VBA

Sub Usage() 
    Dim Py As Object 
    Set Py = CreateObject("Python.MyModule") 

    Dim result 
    result = Py.MyFunction(Array(5, 6, 7, 8)) 
End Sub 

的Pythonmyserver.exemyserver.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) 

请注意,你必须不带任何参数注册类,并使其可用于VBA.CreateObject一旦运行该脚本。

这两种解决方案都可以使用pylauncher,并且可以使用numpy.array(data)转换在python中接收的数组。

相关性:

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

+3

哇,我不使用Python,但它令人印象深刻的是,OLE Variant与Python数组可互操作。 –

+0

选项2很好(你给的确切代码),除了结果是某种[variant](https://msdn.microsoft.com/VBA/Language-Reference-VBA/articles/variant-data-type) 。如何将变体转换为表格,记录集或我可以使用的其他对象?或者它已经在一些可用的形式?我只是不熟悉VBA。 –

+1

取决于返回的内容,封送拆分器将python数组转换为1或2维的变体数组。使用'GetRows'可以直接从记录集中获取数组,但我认为不可能直接添加数组。所以你可能需要遍历数组来添加/更新每条记录。 –

0

你可以尝试加载您的记录集到一个数组,通过循环dim'ed作为双

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

,然后将指针传递给第一个元素PTR = VarPtr(arr(1,1))到Python,其中

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

但VBA仍拥有该阵列存储

+0

等待,VBA将数组存储为列专业,numpy - 相反......除非您的数组是1-dim。 – drgs

相关问题