将numpy数组从VBA移动到Python并返回 [英] Moving numpy arrays from VBA to Python and back
问题描述
我在Microsoft Access中有一个VBA脚本. VBA脚本是具有多个人员的大型项目的一部分,因此无法离开VBA环境.
I have a VBA script in Microsoft Access. The VBA script is part of a large project with multiple people, and so it is not possible to leave the VBA environment.
在脚本的一部分中,我需要快速在表上进行复杂的线性代数运算.因此,我将写为记录集)的VBA表移到Python中做线性代数,然后回到VBA. python中的矩阵表示为numpy
数组.
In a section of my script, I need to do complicated linear algebra on a table quickly. So, I move the VBA tables written as recordsets) into Python to do linear algebra, and back into VBA. The matrices in python are represented as numpy
arrays.
某些线性代数是专有的,因此我们正在使用 pyinstaller 编译专有脚本.
Some of the linear algebra is proprietary and so we are compiling the proprietary scripts with pyinstaller.
该过程的详细信息如下:
The details of the process are as follows:
- VBA脚本创建一个代表表
input.csv
的csv文件. - VBA脚本通过命令行运行python脚本
- python脚本将csv文件
input.csv
作为numpy
矩阵加载,对其进行线性代数运算,并创建输出csv文件output.csv
. - VBA等到python完成后,再加载
output.csv
. - VBA删除不再需要的
input.csv
文件和output.csv
文件.
- The VBA script creates a csv file representing the table
input.csv
. - The VBA script runs the python script through the command line
- The python script loads the csv file
input.csv
as anumpy
matrix, does linear algebra on it, and creates an output csv fileoutput.csv
. - VBA waits until python is done, then loads
output.csv
. - VBA deletes the no-longer-needed
input.csv
file andoutput.csv
file.
此过程效率低下.
有没有一种方法可以在没有csv混乱的情况下将VBA矩阵加载到Python中(并返回)?这些方法是否可以通过pyinstaller与已编译的python代码一起使用?
我发现了以下有关stackoverflow的示例.但是,它们没有具体解决我的问题.
I have found the following examples on stackoverflow that are relevant. However, they do not address my problem specifically.
推荐答案
解决方案1
要么检索Access的COM运行实例,然后通过COM API使用python脚本直接获取/设置数据:
Solution 1
Either retrieve the COM running instance of Access and get/set the data directly with the python script via the COM API:
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])
解决方案2
或创建一个COM服务器以向Access公开某些功能:
Solution 2
Or create a COM server to expose some functions to 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
或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)
请注意,您必须在没有任何参数的情况下运行一次脚本才能注册该类并将其提供给VBA.CreateObject
.
Note that you'll have to run the script once without any argument to register the class and to make it available to VBA.CreateObject
.
两个解决方案都可以使用pylauncher
进行操作,并且可以使用numpy.array(data)
转换python中接收到的数组.
Both solutions work with pylauncher
and the array received in python can be converted with numpy.array(data)
.
依赖性:
https://pypi.python.org/pypi/pywin32
这篇关于将numpy数组从VBA移动到Python并返回的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!