簡體   English   中英

將 numpy 數組從 VBA 移動到 Python 並返回

[英]Moving numpy arrays from VBA to Python and back

我在 Microsoft Access 中有一個 VBA 腳本。 VBA 腳本是多人參與的大型項目的一部分,因此無法離開 VBA 環境。

在我的腳本的一部分中,我需要快速在桌子上做復雜的線性代數。 因此,我將作為 記錄集編寫的 VBA 表移到 Python 中進行線性代數,然后再移回 VBA。 python 中的矩陣表示為numpy數組。

一些線性代數是專有的,因此我們正在使用pyinstaller編譯專有腳本。

詳細過程如下:

  1. VBA 腳本創建一個代表表input.csv的 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

解決方案 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

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

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

您可以嘗試將您的記錄集加載到一個數組中,調暗為 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM