Parameterübergabe von VBA an Python scheitert

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Hallo zusammen,

aus VBA heraus möchte ich mit Hilfe eines Shell-Objekts Python-Code ausführen. Dafür möchte ich der Shell einen Pfad übergeben, der darauf verweist wo das entsprechende Python-Skript liegt. Ich verwende dafür einen Code, den ich vor längerer Zeit mal mit Python 3.7/3.8 verwendet hatte und der damals immer einwandfrei funktioniert hat. Das war allerdings auf einem anderen Rechner muss ich dazu sagen. Mittlerweile hab ich ein Surface Book 3 und verwende Python 3.12.

Und nun will es einfach nicht klappen, den Pfad an Python zu übergeben, zumindest nicht mit der shell.exec-Methode. Das Python-Terminal öffnet sich zwar, aber es bleibt einfach leer und es passiert nichts. Mit Shell.run oder Call Shell funktioniert es hingegen, da ist dann aber das Auslesen der Rückgabewerte wieder wesentlich komplizierter als mit dem Exec-Objekt.

Ich nehme an es hat etwas mit der Syntax zu tun, vielleicht ist die Anordnung der Anführungszeichen beim Übergegeben falsch oder etwas ähnliches.

Im folgenden mein VBA-Code:

Code: Alles auswählen

Sub runPhyton()

Dim pfad As String, wsh As Object, oExec As Object

pfad = ThisWorkbook.Path & "\Test.py" 'Test.py befindet sich im selben Ordner wie das Excel-Workbook

' Shell instanzieren und Pfad zum Python-Skript (Test.py) übergeben

Set wsh = VBA.CreateObject("WScript.Shell")

Set oExec = wsh.Exec("C:\Users\xxxx\AppData\Local\Programs\Python\Python312\python.exe" & " " & pfad)

End Sub
Zur Vollständigkeit noch der Code von Test.py:

Code: Alles auswählen

import sys

arglist = sys.argv # Übergebene Werte aus VBA

print(arglist)

input('Warten auf Eingabe...')
Gibt es irgendwelche Besonderheiten, die man bei der Übergabe von Parametern an eine Python-Shell beachten muss?

Beste Grüße
Marc
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Wenn es mit der einen Methode geht und mit der anderen nicht klingt das für mich eher nach einem Problem, dass in einem VB-Forum behandelt werden sollte.
Ich glaube, es ist in jeder Programmiersprache ein bisschen seltsam, eine Zechenkette zu definieren und dann ein Leerzeichen anzuhängen statt gleich das Leerzeichen mit in die Zeichenkette aufzunehmen.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Vielen Dank! Im VBA-Forum hatte niemand eine Lösung für das Problem und mir scheint das auch eher ein Python-spezifisches Problem, denn Python erwartet offenbar eine ganz spezielle Art, wie man Parameter übergibt.

Wie gesagt, lief mein VBA-Skript mit Python 3.7 einwandfrei. Es scheint mir also, dass an Python 3.12 etwas anders ist.
Benutzeravatar
grubenfox
User
Beiträge: 606
Registriert: Freitag 2. Dezember 2022, 15:49

enthält Leerzeichen? Dann fehlen vermutlich rund um noch Anführungszeichen.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Besten Dank! Da wird der Hase begraben liegen.

Werde es morgen testen.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Ich habe mal versucht den Tipp von grubenfox umzusetzen. Den Pfad in Aführungszeichen zu setzen hat zwar keinen Erfolg gebracht aber ich habe mal versucht den Pfad zwischen zwei Hochkommas zu setzen und habe daraufhin eine interessante Fehlemeldung erhalten:


C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\python.exe: can't open file "C:\\Users\\xxxxx\\Documents\\'C:\\Users\\xxxxx\\Desktop\\Options\\Test.py'": [Errno 22] Invalid argument



Schön zu sehen ist, dass die Shell offenbar versucht eine Datei zu öffnen. Bemerkenswert ist jedoch, dass ich den Teil C:\\Users\\xxxxx\\Documents\\ nicht bewusst an Python übergeben habe, sondern nur den darauffolgenden Pfad.

Vermutlich ist das aber doch eher eine Frage fürs VBA-Forum.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Allerdings kommt mir gerade der Gedanke, dass ich diese Fehlermeldung falsch interpretiere:
C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\python.exe: can't open file "C:\\Users\\xxxxx\\Documents\\'C:\\Users\\xxxxx\\Desktop\\Options\\Test.py'": [Errno 22] Invalid argument
Worauf bezieht sich denn die Meldung "can't open file"?

Bezieht sie sich auf "C:\Users\xxxxx\AppData\Local\Programs\Python\Python312\python.exe:"? (Würde ja bedeuten, dass python.exe gar nicht geöffnet wird.)

Oder bezieht sie sich auf "C:\\Users\\xxxxx\\Documents\\'C:\\Users\\xxxxx\\Desktop\\Options\\Test.py"?
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Die Meldung kommt von Python und bezieht sich auf den *.py-Dateipfad.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1526
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

Ich denke dein ursprüngliche Pfadangabe war nicht falsch, jetzt sieht sie falsch aus.

Zur SIcherheit, was gibt denn der Code aus:

Code: Alles auswählen

Sub runPhyton()

Dim pfad As String, wsh As Object, oExec As Object

pfad = ThisWorkbook.Path & "\Test.py" 'Test.py befindet sich im selben Ordner wie das Excel-Workbook

MsgBox("C:\Users\xxxx\AppData\Local\Programs\Python\Python312\python.exe " & pfad)

End Sub
Musst halt deine `xxxx` noch anpassen.



Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Danke, aber xxxx steht da einfach nur weil ich hier im Netz unterwegs bin. In meinem Code steht natürlich nicht xxxxx.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Hat jemand eine Idee, wie es passieren kann, dass Python - oder vielleicht auch VBA - meinem ursprünglichen Pfad einfach etwas hinzugefügt hat?

Der Pfad, den ich übergebe an Python lautet: "C:\\Users\\xxxxx\\Desktop\\Options\\Test.py"

Und in der Fehlermeldung von Python lautet der Pfad dann plötzlich:

"C:\\Users\\xxxxx\\Documents\\'C:\\Users\\xxxxx\\Desktop\\Options\\Test.py"

Wie kann denn sowas passieren?
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Du übergibst einen relativen Pfad 'C:\\Users\\xxxxx\\Desktop\\Options\\Test.py', denn 'C: wird als Verzeichnis betrachtet, auch wenn : in Pfadnamen nicht erlaubt ist. Und das wird dann mit dem aktuellen Arbeitsverzeichnis zu einem absoluten Pfad.
Und enthält nun dein xxxx ein Leerzeichen oder nicht? Daran liegt es doch wahrscheinlich.
Ein ›hatte zwar keinen Erfolg gehabt‹ ist keine Fehlerbeschreibung, mit der hier irgendjemand was anfangen kann. Was hast Du alles ausprobiert, mit welchem Ergebnis?
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

Hallo Sirius,

vielen Dank! "xxxx" enthält übrigens kein Leerzeichen und ich habe so viele Varianten ausprobiert, dass es den Rahmen sprengen würde, die hier alle aufzuzählen. Deine Erläuterung bezüglich der Frage, weshalb Python dem von mir übergebenen Pfad einfach etwas hinzufügt kann ich leider nicht folgen aber vielleicht kannst Du mir verraten wie ich verhindere, dass Python einfach etwas hinzufügt, dann wäre mein Problem vermutlich gelöst.
Benutzeravatar
grubenfox
User
Beiträge: 606
Registriert: Freitag 2. Dezember 2022, 15:49

Ich vermute der Schuldige ist hier Wsh.Exec bzw. die WScript.Shell und nicht Python.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

@Pythagon: Ed ist ziemlich demotivierend, wenn du schreibst, dass du X Varianten bereits ausprobiert hast. Hier wollen Leute helfen und die probieren jetzt einfach deine Varianten nochmal aus...

Im Gegenzug hast du nie gezeigt wie du den Pfad in Anführungszeichen setzt.

Die Antwort auf Dennis89' Frage fehlt auch. Da geht es nicht darum, dass xxx ein komischer Name ist. Es geht darum zu prüfen, ob in Pfad überhaupt der Wert steckt, den du vermutest. Oder ob das nur eine Annahme ist.

Das bringt welchen Fehler?:

Code: Alles auswählen

Sub runPhyton()

Dim pfad As String, wsh As Object, oExec As Object

pfad = ThisWorkbook.Path & "\Test.py" 'Test.py befindet sich im selben Ordner wie das Excel-Workbook

' Shell instanzieren und Pfad zum Python-Skript (Test.py) übergeben

Set wsh = VBA.CreateObject("WScript.Shell")

Set oExec = wsh.Exec("C:\Users\xxxx\AppData\Local\Programs\Python\Python312\python.exe " & chr(34) & pfad & chr(34))

End Sub
Und das hier:?

Code: Alles auswählen

Sub runPhyton()

Dim pfad As String, wsh As Object, oExec As Object

' Shell instanzieren und Pfad zum Python-Skript (Test.py) übergeben

Set wsh = VBA.CreateObject("WScript.Shell")

Set oExec = wsh.Exec("C:\Users\xxxx\AppData\Local\Programs\Python\Python312\python.exe " & chr(34) & "C:\Users\xxxxx\Desktop\Options\Test.py"
 & chr(34))

End Sub
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Du hast laut Fehlermeldung *einzelne* Anführungszeichen um das Argument gesetzt. Die werden einfach Teil des Arguments und damit ist 'C:… kein absoluter Pfad mehr sondern ein relativer. Der fängt nicht mehr mit einem Laufwerksbuchstaben an, sondern mit einem einzelnen Anführungszeichen. Bei relativen Pfaden wird vom Betriebssystem davon ausgegangen, dass die relativ zum aktuellen Arbeitsverzeichnis des Prozesses sind. Python ist so nett in der Fehlermeldung nicht den relativen, sondern den absoluten Pfad anzuzeigen, damit man nicht raten muss.

Dein Programm macht das hier in der Shell:

Code: Alles auswählen

C:\Users\xxxx\AppData\Local\Programs\Python\Python312\python.exe 'C:\Users\xxxxx\Desktop\Options\Test.py'
Die ' haben für die CMD-Shell aber gar keine besondere Bedeutung, das heisst die sind Teil des Dateinamens. Man würde vermuten, dass dann die Fehlermeldung käme, das die Datei nicht gefunden werden kann, aber da ist halt auch ein Doppelpunkt enthalten der nicht Teil eines Laufwerksbuchstaben ist, und Doppelpunkte sind auf Windowsdateisystemen nicht erlaubt. Darum wird da gar nicht erst nach gesucht, sondern die Fehlermeldung ist über ein ungültiges Argument.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Ich glaube übrigens wir suchen hier ein Problem das es gar nicht gibt. Der Code im ersten Beitrag startet sehr wahrscheinlich das Python-Programm schon problemlos, denn da öffnet sich ja das Konsolenfenster.

Dann schreibst Du das bleibt einfach leer, als wenn Du da etwas anderes erwarten würdest. Natürlich bleibt das leer. Das ist erwartbar und war auch unter 3.7 und 3.8 auch schon so, denn das hat überhaupt nichts mit der Python-Version zu tun. Die `Exec()`-Methode leitet die Ein- und Ausgaben des gestarteten Prozesses um. Was das Python-Programm ausgibt, landet dann natürlich *nicht* in dem Konsolenfenster. Und das `input()` wartet auch nicht auf eine Eingabe im Konsolenfenster, sondern auf Daten die vom VBA-Skript an den Prozess übermittelt werden.

Ich würde die Datenübergabe bei der `Exec()`-Methode nicht als einfach bezeichnen, denn man hat hier das typische Problem, dass ein externer Prozess etwas über die Standard-, und die Standardfehlerausgabe ausgeben kann, und man beides *parallel* auslesen muss um Verklemmungen (Deadlocks) zu vermeiden. Keine Ahnung wie man das in VBA einfach lösen kann. Threads oder `select()` gibt's da wahrscheinlich nicht‽ Am einfachsten wäre es also beim Aufruf des externen Programms die Standardfehlerausgabe in die Standardausgabe umzuleiten.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
grubenfox
User
Beiträge: 606
Registriert: Freitag 2. Dezember 2022, 15:49

Pythagon hat geschrieben: Sonntag 7. Juli 2024, 21:26 Und nun will es einfach nicht klappen, den Pfad an Python zu übergeben, zumindest nicht mit der shell.exec-Methode. Das Python-Terminal öffnet sich zwar, aber es bleibt einfach leer und es passiert nichts. Mit Shell.run oder Call Shell funktioniert es hingegen, da ist dann aber das Auslesen der Rückgabewerte wieder wesentlich komplizierter als mit dem Exec-Objekt.
Wenn der Pfad zur Python-Datei keine Leerzeichen enthält, dann funktioniert es mit shell.exec-Methode genau so wie im ersten Posting angegeben!
Nur das

Code: Alles auswählen

input('Warten auf Eingabe...')
in test.py stört! Ich habe keine Ahnung wohin das ausgegeben wird und wo er dann auf einen Tastendruck wartet, aber jedenfalls nicht in dem sichtbaren Python-Terminal was sich dann öffnet...
[Ergänzung: jetzt wo ich diesen Text abgeschickt habe habe, sehe ich dasa __blackjack__ die Frage schon beantwortet hat. ]

Ich war mal so frei und habe das Test.py ein wenig umgebaut:

Code: Alles auswählen

import sys
import time
from pathlib import Path

arglist = sys.argv # Übergebene Werte aus VBA

print('Argumente:', arglist)

# input('Warten auf Eingabe...')
p = Path('aufruf.log')
with p.open(mode='a') as f:
    print(time.monotonic(), ' hier ist was', file=f)
Weil die Pfadangabe der 'aufruf.log' nur ein relativer ist (ohne Laufwerk), übernehme ich jetzt keine Garantie wo genau die Datei nun angelegt wird. Hoffnungsweise in dem Verzeichnis wo die test.py und die Excel-Datei herum liegen. Hier bei mir wird sie jedenfalls angelegt und bei jedem Klick auf den Knopf im Excel-Dokument wird ein Log-Eintrag angehängt.

Leerzeichen im Pfad zur Test.py test ich jetzt nicht mehr. Das überlasse ich anderen Interessierten.
Pythagon
User
Beiträge: 63
Registriert: Mittwoch 3. Juli 2019, 18:21

@grubenfox:

In der Tat wird Test.py ausgeführt und das Logfile wird abgelegt.

Ich hatte bereits eine Vermutung, wo es abgelegt wird, aufgrund der oben gezeigten Fehlermeldung, die ich vorgestern erhalten hatte. Genau dort lag es dann auch, unter C:\...\Dokumente

Sehr irreführend für einen Anfänger ist, dass der Aufruf Shell.run im Ergebnis dazu führt, dass das aufgerufene Terminal auf Eingabe wartet. Daher hatte ich beim Aufruf über Shell.Exec mit dem selben Verhalten gerechnet.

Ich bedanke mich ganz herzlich bei allen für eure Hilfe!
Benutzeravatar
grubenfox
User
Beiträge: 606
Registriert: Freitag 2. Dezember 2022, 15:49

Hat da wirklich das Terminal auf Eingabe gewartet oder war es das Test.py welches wegen `input('Warten auf Eingabe...')` auf Eingabe wartete?
Shell.run macht eben was anderes als Shell.Exec (sonst wäre ja auch einer der beiden überflüssig)

An die von __blackjack__ beschriebene Verknotung von Standardeingabe und -ausgabe usw... hatte ich auch nicht mehr gedacht. Shell.run macht es offenbar anders.
Antworten