2017-07-14 74 views
6

Mam skrypt VBA w programie Microsoft Access. Skrypt VBA jest częścią dużego projektu z wieloma osobami, więc nie można opuścić środowiska VBA.Przenoszenie numpy tablic z VBA do Pythona iz powrotem

W sekcji mojego skryptu muszę szybko wykonać skomplikowaną algebrę liniową na stole. Tak więc przenoszę tabele VBA napisane jako recordsets) do Pythona, aby wykonać algebrę liniową iz powrotem do VBA. Macierze w pythonie są reprezentowane jako tablice numpy.

Niektóre z algebry liniowych są zastrzeżone, więc kompilujemy własnościowe skrypty z pyinstaller.

Szczegóły tego procesu są następujące:

  1. Skrypt VBA tworzy plik csv reprezentujący tabelę input.csv.
  2. skrypt VBA uruchamia skrypt Pythona poprzez linię poleceń
  3. Skrypt Pythona ładuje plik csv input.csv jako numpy matrycy, czy algebry liniowej na nim, i tworzy plik csv wyjściowe output.csv.
  4. VBA czeka, aż zostanie wykonany pyton, a następnie załaduje output.csv.
  5. Program VBA usuwa niepotrzebny plik input.csv i plik output.csv.

Ten proces jest nieefektywny.

Czy istnieje sposób ładowania macierzy VBA do Pythona (iz powrotem) bez bałaganu CSV? Czy te metody działają ze skompilowanym kodem Pythona przez program piorący?

Znalazłem następujące przykłady na stackoverflow, które są istotne. Jednak nie odnoszą się one konkretnie do mojego problemu.

Return result from Python to Vba

How to pass Variable from Python to VBA Sub

+0

Jeśli skrypt VBA jest tam po prostu stworzyć csv z tabel, przekazać go do Python, wracaj z Python i usuwać resztki, to wygląda jak tam jest nie musisz uruchamiać swojego kodu z VBA. Czy wiesz, że możesz [podłączyć bazę danych MS Access w python] (https://stackoverflow.com/questions/853370/what-do-i-need-to-read-microsoft-access-databases-using-python) używając [ pyodbc] (https://pypi.python.org/pypi/pyodbc/)/[PYPYODBC] (https://pypi.python.org/pypi/pypyodbc)? Jeśli nadal potrzebujesz VBA do swojego celu, wyjaśnij. – Tehscript

Odpowiedz

5

Rozwiązanie 1

Albo pobrać COM uruchomione wystąpienie Access i get/set dane bezpośrednio ze skryptu Pythona za pośrednictwem interfejsu API COM:

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]) 

Rozwiązanie 2

Albo stworzyć serwer COM narazić niektóre funkcje: Dostęp:

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 lub 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) 

Zauważ, że musisz uruchomić skrypt raz bez żadnego argumentu do zarejestrowania klasę i udostępnić go do VBA.CreateObject.

Oba rozwiązania działają z pylauncher, a tablicę odebraną w pythonie można przekonwertować za pomocą numpy.array(data).

Zależność:

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

+3

wow, nie używam Pythona, ale robi wrażenie, że Wariant OLE jest współdziałający z tablicami Pythona. –

+0

Opcja 2 działa super (dokładny kod dałeś), chyba, że ​​wynik jest jakaś [wariant] (https://msdn.microsoft.com/VBA/Language-Reference-VBA/articles/variant-data-type) . Jak zmienić wariant w tabelę, zestaw rekordów lub inny obiekt, którego mogę użyć? Czy jest już w jakiejś użytecznej formie? Po prostu nie jestem zaznajomiony z VBA. –

+1

Marshaler konwertuje tablicę Pythona do 1 lub 2 wymiarów tablicy wariantów w zależności od tego, co powrócisz. Możliwe jest bezpośrednie pobranie tablicy z zestawu rekordów za pomocą 'GetRows', ale nie sądzę, że można bezpośrednio dodać tablicę. Więc prawdopodobnie będziesz musiał przechodzić przez tablicę, aby dodać/zaktualizować każdy rekord. –

0

Można spróbować ustawić ładowanie Twój rekord do tablicy, dim'ed jako podwójne

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

przez pętlę, a następnie przekazać wskaźnik do pierwszego elementu ptr = VarPtr (ARR (1, 1)) pythonowa gdzie

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

Ale VBA będzie nadal właścicielem pamięć tablicy

+0

Czekaj, VBA przechowuje tablice jako kolumna-major, numpy - przeciwnie ... O ile twoja tablica ma 1-dim. – drgs