将numpy数组从VBA移动到Python并返回

Pau*_*ger 8 python vba numpy

我在Microsoft Access中有一个VBA脚本.VBA脚本是具有多个人的大型项目的一部分,因此无法离开VBA环境.

在我的脚本的一部分中,我需要快速地在桌面上进行复杂的线性代数.因此,我将编写为记录集的VBA表移动到Python中以执行线性代数,然后返回到VBA.python中的矩阵表示为numpy数组.

一些线性代数是专有的,因此我们使用pyinstaller编译专有脚本.

该过程的细节如下:

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

这个过程效率低下.

有没有办法在没有csv混乱的情况下将VBA矩阵加载到Python(和后面)?这些方法是否可以通过pyinstaller使用已编译的python代码?

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

将结果从Python返回给Vba

如何将变量从Python传递给VBA Sub

Flo*_* B. 7

解决方案1

检索运行Access的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
Run Code Online (Sandbox Code Playgroud)

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])
Run Code Online (Sandbox Code Playgroud)

解决方案2

或者创建一个COM服务器以向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
Run Code Online (Sandbox Code Playgroud)

Python(myserver.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)
Run Code Online (Sandbox Code Playgroud)

请注意,您必须在没有任何参数的情况下运行脚本一次以注册该类并使其可用VBA.CreateObject.

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

依赖性:

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

  • 哇,我不使用Python,但令人印象深刻的是,OLE Variant可与Python数组互操作. (3认同)
  • 请注意,您需要返回一个python数组而不是一个numpy数组.您可以使用`data.tolist()`或`numpy.asarray`将其转换回来. (2认同)