![](/img/trans.png)
[英]How to exchange multidimensional arrays from matlab to python/numpy and back?
[英]Moving numpy arrays from VBA to Python and back
我在 Microsoft Access 中有一個 VBA 腳本。 VBA 腳本是多人參與的大型項目的一部分,因此無法離開 VBA 環境。
在我的腳本的一部分中,我需要快速在桌子上做復雜的線性代數。 因此,我將作為 記錄集編寫的 VBA 表移到 Python 中進行線性代數,然后再移回 VBA。 python 中的矩陣表示為numpy
數組。
一些線性代數是專有的,因此我們正在使用pyinstaller編譯專有腳本。
詳細過程如下:
input.csv
的 csv 文件。input.csv
加載為numpy
矩陣,對其執行線性代數,並創建輸出 csv 文件output.csv
。output.csv
。input.csv
文件和output.csv
文件。這個過程是低效的。
有沒有一種方法可以在沒有 csv 混亂的情況下將 VBA 矩陣加載到 Python 中(並返回)? 這些方法是否適用於通過 pyinstaller 編譯的 python 代碼?
我在 stackoverflow 上找到了以下相關示例。 但是,他們沒有專門解決我的問題。
要么檢索 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
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])
或者創建一個 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
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
。
這兩種解決方案都適用於pylauncher
,並且可以使用numpy.array(data)
轉換在 python 中接收的數組。
依賴:
您可以嘗試將您的記錄集加載到一個數組中,調暗為 Double
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 仍將擁有數組內存
使用 xlwings 有一種非常簡單的方法可以做到這一點。 請參閱 xlwings.org 並確保按照說明啟用宏設置,在 VBA 參考中勾選 xlwings 等。
然后代碼看起來像下面一樣簡單(一個有點傻的代碼塊,只返回相同的數據幀,但你明白了):
import xlwings as xw
import numpy as np
import pandas as pd
# the @xw.decorator is to tell xlwings to create an Excel VBA wrapper for this function.
# It has no effect on how the function behaves in python
@xw.func
@xw.arg('pensioner_data', pd.DataFrame, index=False, header=True)
@xw.ret(expand='table', index=False)
def pensioner_CF(pensioner_data, mortality_table = "PA(90)", male_age_adj = 0, male_improv = 0, female_age_adj = 0, female_improv = 0,
years_improv = 0, arrears_advance = 0, discount_rate = 0, qxy_tables=0):
pensioner_data = pensioner_data.replace(np.nan, '', regex=True)
cashflows_df = pd.DataFrame()
return cashflows_df
我很想知道這是否能回答問題。 它確實讓我的 VBA / python 體驗變得更容易。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.