VBA --> Python --> VBA?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
JeanCantos
User
Beiträge: 25
Registriert: Sonntag 26. November 2006, 17:08

Hallo zusammen,

habe dieses WE mit Python angefangen und bin bisher begeistert, wie einfach bestimmte Probleme dort gehandhabt werden.

Bisher habe ich unter VBA programmiert, möchte in Zukunft jedoch den größten Teil der Programme in Python schreiben.

Ist es möglich unter VBA Python aufzurufen, Probleme dort lösen zu lassen und den Rückgabewert wieder an VBA zu übergeben?

Habe ein bissel gegoogelt und dort ist das Modul "win32com" des Öfteren aufgetaucht. Bei mir scheint es jedoch nicht installiert zu sein. Wo lassen sich denn benötigte Module finden?

Gruß,

Jean

PS: Arbeite unter WinXP und Office 2003
Benutzeravatar
MoR4euZ
User
Beiträge: 34
Registriert: Mittwoch 18. Oktober 2006, 21:21
Wohnort: Essen
Kontaktdaten:

das ist sicher dieses packet hier

http://sourceforge.net/projects/pywin32/

irgentwo gibt es auch ne online doku dazu
finde die nur nicht auf die schnelle
nach der instalation haste aufjeden fall eine doku ;)

man kann COM objekts damit ansprechen

und viel spass noch mit python ich bin auch seit einigen wochen infiziert :D
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

JeanCantos hat geschrieben:Ist es möglich unter VBA Python aufzurufen, Probleme dort lösen zu lassen und den Rückgabewert wieder an VBA zu übergeben?
Hi JeanCantos!

Willkommen im Python-Forum!

Unter VBS (=Visual Basic Script) kannst du jedes Programm mit ``WScript.Shell.Exec`` aufrufen und die Rückgabe des Programms, also alles was in Python z.B. mit ``print`` ausgegeben wird, verwenden.

Siehe: http://www.microsoft.com/technet/commun ... g1002.mspx
Suche nach "WshScriptExec".

Unter VBA (=Visual Basic for Applications) und "Visual Basic 6" kannst du mit ``ShellWait`` ein Programm aufrufen und warten, bis es abgearbeite ist.

Siehe: http://www.aboutvb.de/khw/artikel/khwshell.htm

Du rufst also einfach vom VB aus das Python-Programm auf. Dabei kannst du Parameter übergeben, die du im Python mit dem Modul ``optparse`` auswerten kannst. Die Rückgabe des Python-Programms (``print``) leitest du in eine Datei um und liest diese Datei danach im VB-Programm aus. Das ist eine einfache Lösung, die ich schon hunderte mal in meinen Programmen eingesetzt habe.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gerold hat geschrieben:Du rufst also einfach vom VB aus das Python-Programm auf.
Ich würde es eher wie MoR4euZ vorschlägt mit COM versuchen, dass ist zwar etwas komplizierter, integriert sich aber viel besser in VB.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
JeanCantos
User
Beiträge: 25
Registriert: Sonntag 26. November 2006, 17:08

Hallo zusammen,

vielen Dank für eure Antworten. Für welche Methode ich mich entscheiden wäre, weiß ich noch nicht.

gerold, Danke für die Links. Ist n e Menge zeug. Magst Du mir den wichtigsten Code hier posten, wie ich aus VBA Python aufrufe und die Paramter übergebe und Rückgabewerte verwerte.

Gruß,

Jean
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

JeanCantos hat geschrieben:den wichtigsten Code hier posten
Hi Jean!

Hier schon mal der Python-Part:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

from optparse import OptionParser
import operator
import sys


def parse_options():
    """
    Wertet die Argumente aus und gibt diese zurück
    """

    # Usage-Text angeben und Parser-Objekt erzeugen
    usage = (
        u"%prog [--addieren | --subtrahieren] NUM [NUM] [...] [-d]\n\n"
        u"    Addiert oder subtrahiert die uebergebenen Nummern.\n\n"
        u"Beispiel: %prog --addieren 100 200"
    )
    parser = OptionParser(usage = usage)

    # Optionen hinzufuegen
    parser.add_option(
        "--addieren",
        dest = "addieren",
        action = "store_true",
        default = True,
        help = "Gibt an, dass die Zahlen addiert werden sollen (Standard)."
    )
    parser.add_option(
        "--subtrahieren",
        dest = "addieren",
        action = "store_false",
        help = "Gibt an, dass die Zahlen subtrahiert werden sollen."
    )
    parser.add_option(
        "-d", "--debug",
        dest = "debug",
        action = "store_true",
        default = False,
        help = "show debug messages"
    )

    # Optionen parsen
    (options, args) = parser.parse_args()

    # Optionen und Argumente anzeigen
    if options.debug:
        print "Options:"
        print options
        print "-"*70
        print "Arguments:"
        for item in args:
            print item
            print "-"*70

    # Rueckgabe
    return (options, args)


def main():
    
    # Optionen und Argumente parsen
    (options, args) = parse_options()
    
    args = [ float(arg) for arg in args ]
    
    if options.addieren:
        sys.stdout.write(str(reduce(operator.add, args)))
    else:
        sys.stdout.write(str(reduce(operator.sub, args)))


if __name__ == "__main__":
    main()
Der VB-Part kommt noch.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
JeanCantos
User
Beiträge: 25
Registriert: Sonntag 26. November 2006, 17:08

Hallo zusammen,

habe mich heute etwas mit der Richtung "Python -> VBA" auseinander gesetzt und muss sagen, dass es bisher mehr oder weniger erfolgreich war. Habe den Code eines Users hier genommen, ein bissel rumgespielt und versucht zu verstehen.

Am Letzteren happert es noch ein bissel, aber ich bin guter Hoffnung. :-)

Code: Alles auswählen

import win32com.client

## Klassendefinition
class Excel:
    def __init__(self,excelpath,Sheet):
        self.excelobj = win32com.client.Dispatch("Excel.Application")
        self.excelobj.Visible = False
        self.wb = self.excelobj.Workbooks.Open(Filename = excelpath, ReadOnly = True)
        self.ws = self.wb.Sheets(Sheet)
    def ReturnValue(self,StartRange,EndRange):
        return self.ws.Range(StartRange,EndRange)
    def close(self):
        del excelobj

## Hauptprogramm
#Oeffnet Excel Datei excel("Pfad und Dateiname mit / und nicht \!!!!,Auswahl des Sheet)
DTCExcel = Excel("D:\Test.xls", "Tabelle1")

Daten = DTCExcel.ReturnValue("A1","B2")

for item in range(0,len(Daten)):
    print item,': ', Daten[item]

##Schließen von Excel
DTCExcel.close
Leider wird meine Excelanwendung mit DTCExcel.close nicht geschlossen. Mag mir jemand erklären, warum nicht.

Gruß,

Jean

PS: gerold, danke für den Code. Der sieht ja mächtig aus und ich irgendwie verstehe ich dort nur Bahnhof. Aber mir reicht es auch erst einmal, wenn ich ihn so übernehmen kann und mit meinem Code ergänzen kann, sodass ich die Probleme, die ich bisher unter VBA gelöst habe, nun mittels Python löse.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hier wäre der "Visual Basic 6"-Teil des Codes.

- Neues Formular erstellen
- Quellcode des Formulars ersetzen
- Ausführen

Code: Alles auswählen

Option Explicit

'************************************************************************
'Deklarationen um den Pfad zu einer temporären Datei heraus zu finden
'************************************************************************
Private Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" ( _
    ByVal nBufferLength As Long, _
    ByVal lpBuffer As String _
) As Long

Private Declare Function GetTempFilename Lib "kernel32" Alias "GetTempFileNameA" ( _
    ByVal lpszPath As String, _
    ByVal lpPrefixString As String, _
    ByVal wUnique As Long, _
    ByVal lpTempFileName As String _
) As Long


'************************************************************************
'Deklarationen für ShellWait
'************************************************************************
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" ( _
    ByVal hProcess As Long, ExitCode As Long _
) As Long
Private Declare Function OpenProcess Lib "kernel32" ( _
    ByVal DesiredAccess As Long, ByVal InheritHandle As Long, ByVal ProcessId As Long _
) As Long


Public Sub Addiere()

Dim sPythonExe As String
Dim sFilename As String
Dim sParams As String
Dim sCommand As String
Dim sTempFilename As String
Dim sResult As String


sPythonExe = "J:\Python24\python.exe"
sFilename = App.Path & "\rechner.py"
sParams = "--addieren 10 20 30.5"

sTempFilename = GetTempFilenameVB()

sCommand = "%COMSPEC% /C " & sPythonExe & " """ & sFilename & """ " & sParams & " > """ & sTempFilename & """"

' Ausführen (unsichtbar)
Call ShellWait(sCommand, vbHide)

' Temp-File auslesen und das Ergebnis anzeigen
If FileExist(sTempFilename) Then
    Open sTempFilename For Input As #1
    Do While Not EOF(1)
       sResult = sResult & Input(1, #1)
    Loop
    Close #1
    If sResult = "" Then
        MsgBox "Keine Daten als Ergebnis.", vbInformation, "Kein Ergebnis"
    Else
        Print sResult
    End If
Else
    MsgBox "Keine Daten als Ergebnis.", vbInformation, "Kein Ergebnis"
End If

' Temp-File löschen
If sTempFilename <> "" Then
    If FileExist(sTempFilename) Then
        Kill sTempFilename
    End If
End If


End Sub


'****************************************************************************
'* Erstellt von: Gerold Penz      Am: 28.05.1998 05:49:29
'* Beschreibung: Prüft ob eine Datei existiert
'****************************************************************************
Public Function FileExist(sDateiname As String) As Boolean
On Error GoTo FileExist_Fehler


FileExist = Dir$(sDateiname) <> ""


FileExist_Exit:
Exit Function

FileExist_Fehler:
FileExist = False
Resume Next

End Function


'***************************************************************************************************
'* Erstellt von: Gerold Penz      Am: 10.06.2005 15:19:26
'* Beschreibung: Wie Shell, wartet aber bis der Prozess zuende ist und gibt
'*               den Rückgabewert zurück
'* Rückgabe: Errorlevel des aufgerufenen Prozesses
'* Parameter: sExec = Programmaufruf
'*            WindowStyle = Maximiert, Normal im Vordergrund, ...
'***************************************************************************************************
Public Function ShellWait( _
    ByVal sExec As String, _
    Optional ByVal WindowStyle As VbAppWinStyle = vbMinimizedFocus _
) As Long

Dim nTaskId As Long
Dim nHProcess As Long
Dim nExitCode As Long
Dim objRE As Object 'VBScript_RegExp_55.RegExp
Dim L As Long
Dim MCol As Object 'VBScript_RegExp_55.MatchCollection

Const STILL_ACTIVE = &H103
Const PROCESS_QUERY_INFORMATION = &H400
  
  
'*******************************************************************************
'Umgebungsvariablen ersetzen
'*******************************************************************************
Set objRE = CreateObject("VBScript.RegExp")
objRE.IgnoreCase = True
objRE.MultiLine = True
objRE.Global = True
If objRE.Test(sExec) Then
    objRE.Pattern = "\%.*?\%"
    Set MCol = objRE.Execute(sExec)
    If MCol.Count > 0 Then
        For L = 0 To MCol.Count - 1
            Set objRE = CreateObject("VBScript.RegExp")
            objRE.IgnoreCase = True
            objRE.MultiLine = True
            objRE.Global = True
            objRE.Pattern = Replace(MCol(L), "%", "\%")
            sExec = objRE.Replace( _
                sExec, _
                Environ$( _
                    Right(Left(MCol(L), Len(MCol(L)) - 1), Len(MCol(L)) - 2) _
                ) _
            )
        Next L
    End If
End If

'*******************************************************************************
'Ausführen
'*******************************************************************************
nTaskId = Shell(sExec, WindowStyle)
nHProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, nTaskId)
Do
    DoEvents
    GetExitCodeProcess nHProcess, nExitCode
Loop While nExitCode = STILL_ACTIVE
CloseHandle nHProcess
ShellWait = nExitCode


Exitbereich:
On Error Resume Next
Set MCol = Nothing
Set objRE = Nothing
Exit Function

End Function


'***************************************************************************************************
'* Erstellt von: Gerold Penz      Am: 22.04.2001 21:14:50
'* Beschreibung: Gibt einen freien Dateinamen für eine temporäre Datei zurück
'* Rückgabe: Dateiname
'***************************************************************************************************
Public Function GetTempFilenameVB() As String

Dim sTempPath_ As String * 256
Dim sTempPath As String
Dim sTempFile_ As String * 256
Dim sTempFile As String
Dim L As Long
Dim sRandom As String


'************************************************************************
'Temporäre Datei herausfinden
'************************************************************************
Randomize
sRandom = CStr(Int((9999 * Rnd) + 1000))   ' Zufallszahl im Bereich von 1000 bis 9999 generieren.
L = GetTempPath(255, sTempPath_)
sTempPath = Left(sTempPath_, InStr(1, sTempPath_, Chr$(0)) - 1)
L = GetTempFilename(sTempPath, sRandom, 1, sTempFile_)
GetTempFilenameVB = Left(sTempFile_, InStr(1, sTempFile_, Chr$(0)) - 1)


End Function


Private Sub Form_Activate()

Static B As Boolean


If Not (B) Then
    B = True
    Call Addiere
End If


End Sub
Ich weiß, das ist eine harte Nuss.

Für die Verwendung in einem VBA-Programm braucht es nur ein paar kleine Änderungen.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
JeanCantos
User
Beiträge: 25
Registriert: Sonntag 26. November 2006, 17:08

Hallo gerold,

danke erst einmal für deine Hilfe und den Code.

Im Augenblick werde ich jedoch von ihm erschlagen und weiß nicht so recht, ob ich mein Anliegen eventuell missverständlich ausgedrückt habe.

Ich habe eher an folgende Vorgehensweise gedacht:

- Ich übergebe in einer Zelle (z.B. "B3) an ein Makro.
- In dem Makro übergebe ich den Inhalt von B3 nun an Python.
- Python bearbeitet den Inhalt von B3
- Python gibt das Resultat der Bearbeitung an mein Makro zurück
- Das Makro schreib das Ergebnis in eine entsprechende Spalte

Ich versuche es einmal an einem einfachen Beispiel zu verdeutlichen:
Stell Dir vor, ich möchte die Zellen B3 bis B26 jeweils mit 5 muliplizieren und in Zelle C3 bis C26 schreiben. Also schreibe ich in Zelle C3 meine Formel "=MultiBlaBla(B3)" auf und ziehe diese Zelle in Excel nun bis C26.

In Python möchte ich nun diese Multiplikation erledigen lassen.

Wie sieht das Prozedere hierbei nun aus?

Wenn ich es anhand dieses einfachen Beispiels verstanden habe, denke ich, dass ich auch komplexere Probleme lösen kann.

Gruß,

Jean
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

JeanCantos hat geschrieben:Leider wird meine Excelanwendung mit DTCExcel.close nicht geschlossen. Mag mir jemand erklären, warum nicht.
Ungetestet, aber so sollte es funktionieren.

Code: Alles auswählen

import win32com.client

## Klassendefinition
class Excel:
    def __init__(self,excelpath,Sheet):
        self.excelobj = win32com.client.Dispatch("Excel.Application")
        self.excelobj.Visible = False
        self.wb = self.excelobj.Workbooks.Open(Filename = excelpath, ReadOnly = True)
        self.ws = self.wb.Sheets(Sheet)
    def ReturnValue(self,StartRange,EndRange):
        return self.ws.Range(StartRange,EndRange)
    def close(self):
        self.excelobj.Quit()
        del self.excelobj

## Hauptprogramm
#Oeffnet Excel Datei excel("Pfad und Dateiname mit / und nicht \!!!!,Auswahl des Sheet)
DTCExcel = Excel("D:\Test.xls", "Tabelle1")

Daten = DTCExcel.ReturnValue("A1","B2")

for item in range(0,len(Daten)):
    print item,': ', Daten[item]

##Schließen von Excel
DTCExcel.close()
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
JeanCantos
User
Beiträge: 25
Registriert: Sonntag 26. November 2006, 17:08

Hi MoR4euZ,

lässt sich denn das Modul win32.client auch einbinden, ohne das komplette Paket PyWin zu installieren?

Welcher Entwicklungsumgebung ist denn für Python zu empfehlen. Der PyWin Editor gefällt mir irgendwie nicht.

Gruß,

Jean
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

JeanCantos hat geschrieben:lässt sich denn das Modul win32.client auch einbinden, ohne das komplette Paket PyWin zu installieren?
Nein.
JeanCantos hat geschrieben:Welcher Entwicklungsumgebung ist denn für Python zu empfehlen. Der PyWin Editor gefällt mir irgendwie nicht.
vim, SciTE, etc. Aber such mal im Forum, denn sowas wird wirklich oft gefragt und es macht keinen Sinn das immer neu auszudiskutieren.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

JeanCantos hat geschrieben:Stell Dir vor, ich möchte die Zellen B3 bis B26 jeweils mit 5 muliplizieren und in Zelle C3 bis C26 schreiben. Also schreibe ich in Zelle C3 meine Formel "=MultiBlaBla(B3)" auf und ziehe diese Zelle in Excel nun bis C26.
In Python möchte ich nun diese Multiplikation erledigen lassen.
Wie sieht das Prozedere hierbei nun aus?
Hi Jean!

Schwierige Sache!!! Es ist nämlich so, dass du dir ziemlich schnell die Geschwindigkeit von Excel zusammen hauen kannst.

Die Methode, die ich vorgeschlagen habe, läuft ja so, dass jedes Kommandozeilenprogamm (nicht nur Python-Programme) damit aufgerufen werden kann. Allerdings ist jeder Aufruf ein neuer Prozess. Zuerst wird also Python gestartet und dann das Python-Skript abgearbeitet. Wenn du eine Funktion in ein paar hundert Excel-Zellen drinnen hast, dann wird die Neuberechnung der Zellen quälend langsam.

Die schnellere Methode wäre, wenn du dir ein COM-Serverprogramm schreiben würdest. Wie das funktioniert, findest du in der Hilfe zu "pywin32" (Suche nach "COM"; nur Titel). Ohne "pywin32" bist du unter Windows sowiso aufgeschmissen. Das musst du sowiso installieren. -- Ist also kein Nachteil! COM hat den faden Beigeschmack, dass es nur unter Windows funktioniert. -- Excel funktioniert auch "nur" unter Windows, also ist das kein so großes Problem.
Der (für mich) größte Nachteil ist, dass ein COM-Serverprogramm in der Registry registriert werden muss. Das ist zwar sehr einfach, aber es katapultiert dich wieder in die DLL-Hell zurück. Man weiß nie, welche DLL jetzt gerade aktuell registriert ist... Du musst also sicher stellen, dass die gewünschte Python-Datei zuerst registriert wird, bevor du damit korrekt arbeiten kannst.
Allerdings ist es dadurch möglich, das Python-Programm nur einmal zu laden und die Funktionen mehrfach auszuführen. Das ist wesentlich schneller als der Aufruf über die Kommandozeile.

Wenn du also eine Python-Funktion *oft* aufrufen musst, dann kommst du um COM nicht herum. Die Kommandozeilen-Schnittstelle, die ich weiter oben aufgezeigt habe, verwende ich nur in reinen "Visual Basic 6"-Programmen und nur zum Aufrufen von Tools, Berichten, Auswahldialogen, usw. Da kommt es nicht auf Geschwindigkeit an.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Antworten