Schreiben in eine geöffnete Excel Datei
Huch, kann das etwa tatsächlich in eine geöffnete Datei schreiben, indem es sich an die laufende Excel-Anwendung dran stöpselt oder so?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
So... "ernsthaft getestet" würde ich es immer noch nicht nennen, aber wenigsten weiß ich jetzt wo von ich rede

Voraussetzungen: xlwings muss natürlich installiert sein (vom installierten Python gehe ich mal einfach aus)
Python-Datei mit Namen mach_was.py:
Code: Alles auswählen
# mach_was.py
import xlwings as xw
def erzeuge_daten():
neue_daten = []
for monat in range(1,61):
neue_liste = []
for parameter in range(1,5):
neue_liste.append(parameter * monat)
neue_daten.append(neue_liste)
wb = xw.Book.caller()
wb.sheets[0]['A1'].value = neue_daten
Bei den Berechnungen durchlaufe ich eine Schleife, die nacheinander 240 Werte berechnet (für vier Parameter jeweils 60 Monate). Das bedeutet ich habe eine 4*60 Matrix.
leere Excel-Datei versuch.xls:
Datei in Excel öffnen, VBA-Editor öffnen , unter Menüpunkt "Extra/Verweise" Haken bei 'xlwings' setzen
VBA-Routine erzeugen:
Code: Alles auswählen
Sub StarteVersuch()
RunPython "import mach_was; mach_was.erzeuge_daten()"
End Sub
Zuletzt geändert von grubenfox am Dienstag 5. September 2023, 15:48, insgesamt 2-mal geändert.
_______________________________________________________________________________
https://www.python-kurs.eu/index.php
https://learnxinyminutes.com/docs/python/ https://learnxinyminutes.com/docs/de-de/python-de/
https://quickref.me/python https://docs.python-guide.org/
das kurze Warten kommt vom Starten des Python-Interpreter. Wenn man xlwings sagt dass es den Interpreter im Hintergrund laufen lassen soll (Haken bei "RunPython: Use UDF-Server" setzen), dann dauert es nur beim ersten Ausführen der VBA-Routine einen kurzen Moment... bei weiteren Aufrufen von RunPython entfällt die Wartezeit.
_______________________________________________________________________________
https://www.python-kurs.eu/index.php
https://learnxinyminutes.com/docs/python/ https://learnxinyminutes.com/docs/de-de/python-de/
https://quickref.me/python https://docs.python-guide.org/
Wenn ich das richtig interpretiere ist zum BEARBEITEN mindestens $windows & ein laufendes excel obligatorisch, ODER M$-bezahl-versionen die dann auch unter linux irgendwo in der Cloud wursteln.__deets__ hat geschrieben: Dienstag 5. September 2023, 15:47 mea culpa, dass xlwings auch die Zusammenarbeit mit einem geoeffneten Excel beherrscht, wusste ich nicht. Das ist dann natuerlich deutlich bequemer, als da mit DCOM selbst rumzuhampeln.
Du hast natürlich komplett recht und auch an Dich noch mal ein großer Dank für Dein Beispielskript. Als Python Anfänger habe ich die steilste Lernkurve an konkreten Umsetzungsbeispielen und ich wusste nicht, wie ich eine verschachtelte Liste machen kann.pillmuncher hat geschrieben: Dienstag 5. September 2023, 14:18 @JaSyMa: In meinem Beispiel habe ich doch gezeigt, wie man ganze Bereiche in Excel einfügt, nicht nur einzelne Werte in einzelne Zellen. Der zugewiesene Wert muss einfach eine Liste von Listen mit denselben Dimensionen sein, wie der ausgewählte Bereich (Range) im Excel Sheet.
Habe das jetzt so gelöst. Bin nicht 100% happy, da ich nicht wusste, wie ich die Gruppen in der ersten Spalte und die Monate in der ersten Zeile gleich mit ins array nehmen kann. Da gab es immer wieder Dimensionierungsfehler aus der Liste. Jetzt schreibe ich die vier Gruppen direkt in die Zelle und nutze das Array für die 240 Berechnungen. Hoffe das sieht so einigermaßen sinnvoll aus.
import win32com.client as com
import numpy as np
excel = com.Dispatch('Excel.Application')
sheet = excel.ActiveSheet
neue_daten = []
for monat in range(1,5):
neue_liste = []
for gruppe in range(1,61):
neue_liste.append(monat)
neue_daten.append(neue_liste)
neue_daten=np.array(neue_daten).T
print (neue_daten)
# data_rows in den ausgewählten Bereich schreiben:
sheet.Range("B2:BI5").Value = neue_daten
Da ich das Array vertikal bestücken muss nutze ich am Ende die Transponierung via numpy.
@grubenfox: Vielen Dank für Deinen Livetest. Ich bin jetzt erst mal den Weg über DCOM weitergegangen, da mir dies auch recht unaufwendig vorkommt und ich meine erste fertige Funktionalität feiern wollte

... andere Frage - ich starte das Python skript aus Excel mit folgendem Code:
Dim objShell As Object
Dim PythonExePath, PythonScriptPath As String
Set objShell = VBA.CreateObject("Wscript.Shell")
PythonExePath = "C:\Python311\python.exe"
PythonScriptPath = "C:\Users\Jan\Desktop\Python\script.py"
objShell.Run PythonExePath & " " & PythonScriptPath
Leider poppt da immer das schwarze cmd Fenster kurz auf. Kennt einer von Euch einen beim Aufruf mitzugebenden Parameter, um das in den Hintergrund zu schieben? In anderen Anwendungen nennt sich das silent mode o.ä...
Dim objShell As Object
Dim PythonExePath, PythonScriptPath As String
Set objShell = VBA.CreateObject("Wscript.Shell")
PythonExePath = "C:\Python311\python.exe"
PythonScriptPath = "C:\Users\Jan\Desktop\Python\script.py"
objShell.Run PythonExePath & " " & PythonScriptPath
Leider poppt da immer das schwarze cmd Fenster kurz auf. Kennt einer von Euch einen beim Aufruf mitzugebenden Parameter, um das in den Hintergrund zu schieben? In anderen Anwendungen nennt sich das silent mode o.ä...
Wenn Du keine Konsole möchtest, dann rufe einfach pythonw.exe auf.
Zum Entwickeln ist es aber einfacher, wenn Du Dein Skript nicht über VBA aufruft, sondern in einer Konsole, so dass Du auch mögliche Fehlermeldungen sehen kannst.
Zum Entwickeln ist es aber einfacher, wenn Du Dein Skript nicht über VBA aufruft, sondern in einer Konsole, so dass Du auch mögliche Fehlermeldungen sehen kannst.
Dank Dir, Sirius3. Beim Entwickeln und debuggen arbeite ich direkt im VS C. Der Übergang zum VBA Call ist dann noch mal eine Sollbruchstelle w/abweichenden Pfaden etc. D.h. da will ich die Konsole im letzten Entwicklungsschritt auch noch sehen (sie soll sogar stehen bleiben, damit ich Fehler sehe - gibt es dafür einen Parameter?)
Nur im Livebetrieb brauche ich die Konsole dann nicht und werde Deinen tip nutzen…
Nur im Livebetrieb brauche ich die Konsole dann nicht und werde Deinen tip nutzen…
JaSyMa hat geschrieben: Dienstag 5. September 2023, 18:16 @grubenfox: Vielen Dank für Deinen Livetest. Ich bin jetzt erst mal den Weg über DCOM weitergegangen, da mir dies auch recht unaufwendig vorkommt und ich meine erste fertige Funktionalität feiern wollteWelche Lösung ist aus Deiner Sicht die bessere bzw. hat xlwings irgendwelche Vor-/Nachtreile?
In Excel unter dem xlwings-Menüpunkt im Feld "Show console" einfach keine Haken setzen...JaSyMa hat geschrieben: Dienstag 5. September 2023, 18:21 ... andere Frage - ich starte das Python skript aus Excel mit folgendem Code:
Leider poppt da immer das schwarze cmd Fenster kurz auf. Kennt einer von Euch einen beim Aufruf mitzugebenden Parameter, um das in den Hintergrund zu schieben? In anderen Anwendungen nennt sich das silent mode o.ä...
Mal so als Antwort auf die zweite und erste Frage. Hat nun beides seine Vor- oder Nachteile: wenn man den Python-Aufruf in VB programmiert, dann kann man das auch programmatisch beeinflussen (ob z.b. nun mit Konsole oder ohne, welches Python überhaupt aufgerufen wird,...). Bei xlwings muss man das alles nicht programmieren, muss das dafür dann händisch einmal einstellen.
Richtig lustig wird es offenbar wenn man irgendwelche in Python programmierten Funktionen als UDFs ("user defined function") einfach in die Tabellenzellen von Excel einträgt...
Statt ein
Code: Alles auswählen
=MAX(A5:D5)
Code: Alles auswählen
=mein_in_python_programmiertes_max(A5:A5000)
Ich gehe mal davon aus dass die eigentliche Berechnungsfunktion im Pythoncode irgendwo als aufrufbare Funktion vorliegt. Dann könntest du mit xlwings vermutlich auch auf die ganzen Schleifen verzichten. Einfach in die Zellen, in denen die berechneten 240 Ergebnisse stehen sollen, die Berechnungsfunktion aufrufen und fertig.
_______________________________________________________________________________
https://www.python-kurs.eu/index.php
https://learnxinyminutes.com/docs/python/ https://learnxinyminutes.com/docs/de-de/python-de/
https://quickref.me/python https://docs.python-guide.org/
Ich habe mir das Buch vom xlwings Schöpfer F. Zumstein geholt. mal schauen, ob ich da noch was spannendes finde.
Im Moment bin ich etwas desillusioniert von der Performance des Pythonskripts. Mit knapp 2s für mein kleines Beispielsample (sind grad mal 23 Datensätze aus denen berechnet wird) ist es langsamer als mein bisheriger VBA Code. Im VBA habe ich zugegeben nicht im Excel gerechnet, sondern übertrage das Testset im Array an den Arbeitsspeicher, rechne dort und hole das Ergebnis zurück. Ich hatte nun gehofft mit Python noch einen Performancesprung hinzukriegen.
Ganz sicher ist mein code sehr "rookie-"haft. Seht Ihr irgendwelche Performancebremsen? Ich habe das gefühl, dass die ganze "Laderei" des Interpreters etc. scho ndauert...
from openpyxl import workbook, load_workbook
import win32com.client as com
import datetime
import time
import os
import numpy as np
zeit_anf = time.time()
excel = com.Dispatch('Excel.Application')
sheet = excel.ActiveSheet
Zielrange = sheet.Range("S2").value #A8
Rentenzeichen = sheet.Range("L71").value #A8
os.chdir(r'C:\Users\Jan\Desktop\Python')
wb=load_workbook("python.xlsx")
ws = wb["Vertraege"]
#print (ws)
#print (str(sheet))
Z1=2
Col1=2 #Spalte mit Beschriftungen (Spalte B)
Col2=Col1 # erste Spalte mit Werten (Spalte C)
# Identifikation der relevanten Spalten
for col in range(1,100):
if ws.cell(row=1, column=col).value == "Änderungsdatum":
erste = col
elif ws.cell(row=1, column=col).value == "letzte":
letzte = col
for row in range(54, 58): #immer eine zeile mehr als gewollt
Gruppe=ws["R" + str(row)].value
# data_rows in den ausgewählten Bereich schreiben:
sheet.Range("A" + str(Z1)).Value = Gruppe
Z1=Z1+1
Z1=2
neue_daten = []
renten_gesamt = []
for col in range(erste+1,letzte+1): # Monate - immer eine Spalte mehr als gewollt
neue_liste = []
Renten_Monat = []
Datum=ws.cell(row=97, column=col).value
#print (Datum)
Summeges=0
#sheet.Range("B1").value = Datum
Col2=Col2+1
for row in range(54, 58): # Gruppen - immer eine zeile mehr als gewollt
Gruppe=ws["R" + str(row)].value
Summe=0
SummeRenten=0
for row1 in range(98,122): # Vertragsliste - immer eine Zeile mehr als gewollt
Gruppe1 = ws["R" + str(row1)].value
VertragAb=ws["N" + str(row1)].value # Datum Beginn Vertrag
AnzJahr=12/ws["Q" + str(row1)].value # Zahltermin p.a.
if VertragAb == None:
Monat = 0
else:
Monat=VertragAb.month
result = (Datum.month+12-Monat) % AnzJahr
if result == 0:
DatumAb=ws["N" + str(row1)].value # Datum Beginn Vertrag
DatumBis=ws["O" + str(row1)].value # Datum Ende Vertrag
if ws["P" + str(row1)].value == "verlängern": DatumBis = None
if DatumAb != None:
if DatumBis != None:
if Datum >= DatumAb and Datum < DatumBis:
WertRel=1
elif DatumBis == None:
if Datum >= DatumAb:
WertRel=1
if WertRel == 1 and Gruppe1 == Gruppe:
Summe = Summe + ws["K" + str(row1)].value
if ws["J" + str(row1)].value == sheet.Range("L71").value:
SummeRenten = SummeRenten + ws["K" + str(row1)].value
neue_liste.append(Summe)
Renten_Monat.append(SummeRenten)
Z1=Z1+1
Summeges=Summeges+Summe
Z1=2
neue_daten.append(neue_liste)
renten_gesamt.append(Renten_Monat)
# array umdrehen
neue_daten=np.array(neue_daten).T
renten_gesamt=np.array(renten_gesamt).T
# data_rows in den ausgewählten Bereich schreiben:
sheet.Range(Zielrange).Value = neue_daten
sheet.Range("B8:BI8").Value = renten_gesamt
wb.close
zeit_end = time.time()
print (zeit_end - zeit_anf)
Im nächsten Schritt wollte ich jetzt eigentlich auf das load_workbook verzichten (weil die Datei eh offen ist) und die Infos aus der offenen Datei holen, die das Pythonskript called. Wollte dazu das "ws" sheet gegen das "com" sheet tauschen. Das scheint aber nicht so einfach zu funktionieren, da es schon bei "sheets.cell(row=..." einen Fehler wirft. Bevor ich da aber weiter knoble will ich erst wissen, ob ich mit Python erhofften Performanceschub hinkriege. Wenn das in dem hier vorliegenden usecase nicht klappt versuche ich lieber einen anderen, bei dem ich diverse csv Dateien einlese und daraus Berechungen anstelle. Dort ist die challenge eher die Menge an Datensätzen in der csv aus denen ich mitr den Performanceschub erhoffe...
Im Moment bin ich etwas desillusioniert von der Performance des Pythonskripts. Mit knapp 2s für mein kleines Beispielsample (sind grad mal 23 Datensätze aus denen berechnet wird) ist es langsamer als mein bisheriger VBA Code. Im VBA habe ich zugegeben nicht im Excel gerechnet, sondern übertrage das Testset im Array an den Arbeitsspeicher, rechne dort und hole das Ergebnis zurück. Ich hatte nun gehofft mit Python noch einen Performancesprung hinzukriegen.
Ganz sicher ist mein code sehr "rookie-"haft. Seht Ihr irgendwelche Performancebremsen? Ich habe das gefühl, dass die ganze "Laderei" des Interpreters etc. scho ndauert...
from openpyxl import workbook, load_workbook
import win32com.client as com
import datetime
import time
import os
import numpy as np
zeit_anf = time.time()
excel = com.Dispatch('Excel.Application')
sheet = excel.ActiveSheet
Zielrange = sheet.Range("S2").value #A8
Rentenzeichen = sheet.Range("L71").value #A8
os.chdir(r'C:\Users\Jan\Desktop\Python')
wb=load_workbook("python.xlsx")
ws = wb["Vertraege"]
#print (ws)
#print (str(sheet))
Z1=2
Col1=2 #Spalte mit Beschriftungen (Spalte B)
Col2=Col1 # erste Spalte mit Werten (Spalte C)
# Identifikation der relevanten Spalten
for col in range(1,100):
if ws.cell(row=1, column=col).value == "Änderungsdatum":
erste = col
elif ws.cell(row=1, column=col).value == "letzte":
letzte = col
for row in range(54, 58): #immer eine zeile mehr als gewollt
Gruppe=ws["R" + str(row)].value
# data_rows in den ausgewählten Bereich schreiben:
sheet.Range("A" + str(Z1)).Value = Gruppe
Z1=Z1+1
Z1=2
neue_daten = []
renten_gesamt = []
for col in range(erste+1,letzte+1): # Monate - immer eine Spalte mehr als gewollt
neue_liste = []
Renten_Monat = []
Datum=ws.cell(row=97, column=col).value
#print (Datum)
Summeges=0
#sheet.Range("B1").value = Datum
Col2=Col2+1
for row in range(54, 58): # Gruppen - immer eine zeile mehr als gewollt
Gruppe=ws["R" + str(row)].value
Summe=0
SummeRenten=0
for row1 in range(98,122): # Vertragsliste - immer eine Zeile mehr als gewollt
Gruppe1 = ws["R" + str(row1)].value
VertragAb=ws["N" + str(row1)].value # Datum Beginn Vertrag
AnzJahr=12/ws["Q" + str(row1)].value # Zahltermin p.a.
if VertragAb == None:
Monat = 0
else:
Monat=VertragAb.month
result = (Datum.month+12-Monat) % AnzJahr
if result == 0:
DatumAb=ws["N" + str(row1)].value # Datum Beginn Vertrag
DatumBis=ws["O" + str(row1)].value # Datum Ende Vertrag
if ws["P" + str(row1)].value == "verlängern": DatumBis = None
if DatumAb != None:
if DatumBis != None:
if Datum >= DatumAb and Datum < DatumBis:
WertRel=1
elif DatumBis == None:
if Datum >= DatumAb:
WertRel=1
if WertRel == 1 and Gruppe1 == Gruppe:
Summe = Summe + ws["K" + str(row1)].value
if ws["J" + str(row1)].value == sheet.Range("L71").value:
SummeRenten = SummeRenten + ws["K" + str(row1)].value
neue_liste.append(Summe)
Renten_Monat.append(SummeRenten)
Z1=Z1+1
Summeges=Summeges+Summe
Z1=2
neue_daten.append(neue_liste)
renten_gesamt.append(Renten_Monat)
# array umdrehen
neue_daten=np.array(neue_daten).T
renten_gesamt=np.array(renten_gesamt).T
# data_rows in den ausgewählten Bereich schreiben:
sheet.Range(Zielrange).Value = neue_daten
sheet.Range("B8:BI8").Value = renten_gesamt
wb.close
zeit_end = time.time()
print (zeit_end - zeit_anf)
Im nächsten Schritt wollte ich jetzt eigentlich auf das load_workbook verzichten (weil die Datei eh offen ist) und die Infos aus der offenen Datei holen, die das Pythonskript called. Wollte dazu das "ws" sheet gegen das "com" sheet tauschen. Das scheint aber nicht so einfach zu funktionieren, da es schon bei "sheets.cell(row=..." einen Fehler wirft. Bevor ich da aber weiter knoble will ich erst wissen, ob ich mit Python erhofften Performanceschub hinkriege. Wenn das in dem hier vorliegenden usecase nicht klappt versuche ich lieber einen anderen, bei dem ich diverse csv Dateien einlese und daraus Berechungen anstelle. Dort ist die challenge eher die Menge an Datensätzen in der csv aus denen ich mitr den Performanceschub erhoffe...
Du gehst mit der falschen Erwartung an die Geschichte ran. Der erste Schritt ist, ein gut wartbares Programm zu schreiben, und das ist mit Python halt deutlich einfacher als mit VBA.
Variabelennamen werden generell klein geschrieben. Sie werden erst initialisiert, wenn man sie auch braucht und nicht schon etliche Zeilen davor.
os.chdir hat in einem sauberen Programm nichts zu suchen.
String stückelt man nicht mit + zusammen, 0 und 1 sind kein Ersatz für False oder True, mit None vergleicht man immer per `is None` oder `is not None`
Diese Kommentare "immer eine Zeile mehr als gewollt" ist falsch, denn bei range ist einfach das Ende exklusiv, Du hast also genau die Anzahl Zeilen, die Du willst.
Es fehlt eine Strukturierung in Funktionen. Sieben Einrückebenen sind einfach viel zu viel.
Erster Schritt wäre es, zuerst die Daten in eine passende Datenstruktur zu überführen, mit der man sauber arbeiten kann, statt sich ständig mit den einzelnen Zellen herumzuschlagen.
Wenn man näher in den Code schaut, gibt es einige Fehler: WertRel wird nicht immer definiert und enthält im Zweifel veraltete werde. Z1 wird in den unteren Schleifen verändert, aber nie benutzt. Col2 und Summeges werden auch nicht benutzt.
VertragAb und DatumAb enthalten den selben Wert.
Das ganze könnte also ungefähr so aussehen:
Variabelennamen werden generell klein geschrieben. Sie werden erst initialisiert, wenn man sie auch braucht und nicht schon etliche Zeilen davor.
os.chdir hat in einem sauberen Programm nichts zu suchen.
String stückelt man nicht mit + zusammen, 0 und 1 sind kein Ersatz für False oder True, mit None vergleicht man immer per `is None` oder `is not None`
Diese Kommentare "immer eine Zeile mehr als gewollt" ist falsch, denn bei range ist einfach das Ende exklusiv, Du hast also genau die Anzahl Zeilen, die Du willst.
Es fehlt eine Strukturierung in Funktionen. Sieben Einrückebenen sind einfach viel zu viel.
Erster Schritt wäre es, zuerst die Daten in eine passende Datenstruktur zu überführen, mit der man sauber arbeiten kann, statt sich ständig mit den einzelnen Zellen herumzuschlagen.
Wenn man näher in den Code schaut, gibt es einige Fehler: WertRel wird nicht immer definiert und enthält im Zweifel veraltete werde. Z1 wird in den unteren Schleifen verändert, aber nie benutzt. Col2 und Summeges werden auch nicht benutzt.
VertragAb und DatumAb enthalten den selben Wert.
Das ganze könnte also ungefähr so aussehen:
Code: Alles auswählen
from openpyxl import workbook, load_workbook
import win32com.client as com
import numpy as np
def lese_monate(ws):
# Identifikation der relevanten Spalten
erste = letzte = None
for col in range(1, 100):
if ws.cell(row=1, column=col).value == "Änderungsdatum":
erste = col
elif ws.cell(row=1, column=col).value == "letzte":
letzte = col
return [
ws.cell(row=97, column=month_col).value
for month_col in range(erste + 1, letzte + 1)
]
def lese_vertraege(ws):
result = []
for row in range(98, 122):
j_wert = ws[f"J{row}"]
betrag = ws[f"K{row}"]
vertrag_ab = ws[f"N{row}"]
if ws[f"P{row}"].value == "verlängern":
vertrab_bis = None
else:
vertrag_bis = ws[f"O{row}"].value # Datum Ende Vertrag
if vertrag_ab is None:
monat = 0
else:
monat = vertrag_ab.month
zahltermine_pro_jahr = [f"Q{row}"]
gruppe = ws[f"R{row}"]
result.append({
"ab": vertag_ab,
"bis": vertrag_bis,
"betrag": betrag,
"j_wert": j_wert,
"monat": monat,
"zahltermine_pro_jahr": zahltermine_pro_jahr,
"gruppe": gruppe,
})
return result
def summiere_vertraege(vertraege, datum, gruppe, l71):
summe = 0
summe_renten = 0
for vertrag in vertraege:
if ((datum.month+12-vertrag["monat"]) % vertrag["zahltermine_pro_jahr"] == 0
and (vertrag["ab"] is not None and vertrag["ab"] <= datum)
and (vertrag["bis"] is None or datum < vertrag["bis"])
and vertrag["gruppe"] == gruppe):
summe += vertrag["betrag"]
if vertrag["j_wert"] == l71:
summe_renten += vertrag["betrag"]
return summe, summe_renten
def main():
wb=load_workbook(r"C:\Users\Jan\Desktop\Python\python.xlsx")
ws = wb["Vertraege"]
vertraege = lese_vertraege(ws)
monate = lese_monate(ws)
gruppen = [ws[f"R{row}"].value for row in range(54, 58)]
wb.close()
excel = com.Dispatch('Excel.Application')
sheet = excel.ActiveSheet
zielrange = sheet.Range("S2").Value
rentenzeichen = sheet.Range("L71").Value
l71 = sheet.Range("L71").Value
neue_daten = []
renten_gesamt = []
for datum in monate:
summen = []
renten_monat = []
for gruppe in gruppen:
summe, summe_renten = summiere_vertraege(vertraege, datum, gruppe, l71)
summen.append(summe)
renten_monat.append(summe_renten)
neue_daten.append(summen)
renten_gesamt.append(renten_monat)
# data_rows in den ausgewählten Bereich schreiben:
sheet.Range("A2:A6").Value = gruppen
sheet.Range(zielrange).Value = np.array(neue_daten).T
sheet.Range("B8:BI8").Value = np.array(renten_gesamt).T
if __name__ == "__main__":
main()
- __blackjack__
- User
- Beiträge: 14028
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Nächste Runde/Iteration: `l71`? Das gibt's doch schon als `rentenzeichen`.
`wb` und `ws` sind keine so tollen Namen. Für `wb` wäre `workbook` besser und `ws` wäre `sheet`. Das gibt es aber schon, also sollten am besten *beide* einen Zusatz bekommen der dem Leser vermittelt was die Arbeitsblätter bedeuten/enthalten. *Oder* man schreibt für das einlesen der Verträge eine eigene Funktion, so das die beiden Arbeitsblätter nicht mehr im gleichen Namensraum existieren müssen.
`workbook` aus `openpyxl` wird importiert aber nirgends verwendet.
Bei den `openpyxl`-Methoden würde ich die Koordinaten nicht immer als Zeichenkette zusammenbasteln. Da muss die Bibliothek dann im nächsten Schritt ja nur wieder zu zwei Zahlen aus der Zeichenkette für den Zugriff auseinandernehmen.
Oder um es einfacher und lesbarer zu machen nicht über den Zeilenindex iterieren und sich dann einzelne Zellen aus dieser Zeile herauspicken, sondern gleich über den relevanten Ausschnitt der Tabelle iterieren. Dann kann man auch *einmal* diesen Bereich oder zumindest die Spaltennamen als Zeichenkette angeben. Dann wird der Schleifeninhalt von `vertraege_lesen()` so einfach, dass man daraus eine „list comprehension“ machen kann.
Wörterbücher die immer den gleichen, festen Satz an Schlüsseln haben sind eigentlich Objekte. Wenn man den Vetrag mit einer Klasse modelliert braucht man den redundanten "monat" nicht mehr, sondern kann den als Property definieren. Und/oder man packt da dann auch gleiche Prüfmethoden drauf und kann die Bedingung etwas sprechender ausdrücken.
Ungetestet:
`wb` und `ws` sind keine so tollen Namen. Für `wb` wäre `workbook` besser und `ws` wäre `sheet`. Das gibt es aber schon, also sollten am besten *beide* einen Zusatz bekommen der dem Leser vermittelt was die Arbeitsblätter bedeuten/enthalten. *Oder* man schreibt für das einlesen der Verträge eine eigene Funktion, so das die beiden Arbeitsblätter nicht mehr im gleichen Namensraum existieren müssen.
`workbook` aus `openpyxl` wird importiert aber nirgends verwendet.
Bei den `openpyxl`-Methoden würde ich die Koordinaten nicht immer als Zeichenkette zusammenbasteln. Da muss die Bibliothek dann im nächsten Schritt ja nur wieder zu zwei Zahlen aus der Zeichenkette für den Zugriff auseinandernehmen.
Oder um es einfacher und lesbarer zu machen nicht über den Zeilenindex iterieren und sich dann einzelne Zellen aus dieser Zeile herauspicken, sondern gleich über den relevanten Ausschnitt der Tabelle iterieren. Dann kann man auch *einmal* diesen Bereich oder zumindest die Spaltennamen als Zeichenkette angeben. Dann wird der Schleifeninhalt von `vertraege_lesen()` so einfach, dass man daraus eine „list comprehension“ machen kann.
Wörterbücher die immer den gleichen, festen Satz an Schlüsseln haben sind eigentlich Objekte. Wenn man den Vetrag mit einer Klasse modelliert braucht man den redundanten "monat" nicht mehr, sondern kann den als Property definieren. Und/oder man packt da dann auch gleiche Prüfmethoden drauf und kann die Bedingung etwas sprechender ausdrücken.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
from contextlib import closing
import numpy as np
import win32com.client as com
from attr import attrib, attrs
from openpyxl import load_workbook
from openpyxl.utils import column_index_from_string
@attrs
class Vertrag:
ab = attrib()
bis = attrib()
betrag = attrib()
rentenzeichen = attrib()
zahltermine_pro_jahr = attrib()
gruppe = attrib()
@property
def monat(self):
return 0 if self.ab is None else self.ab.monat
def ist_faellig_am(self, datum):
return (
(datum.month + 12 - self.monat) % self.zahltermine_pro_jahr == 0
and (self.ab and self.ab <= datum)
and (not self.bis or datum < self.bis)
)
def lese_vertraege(sheet):
return [
Vertrag(
vertrag_ab,
None if verlaengern_text == "verlängern" else vertrag_bis,
betrag,
rentenzeichen,
zahltermine_pro_jahr,
gruppe,
)
for (
rentenzeichen,
betrag,
_,
_,
vertrag_ab,
vertrag_bis,
verlaengern_text,
zahltermine_pro_jahr,
gruppe,
) in sheet.iter_rows(
98,
122,
column_index_from_string("J"),
column_index_from_string("R"),
values_only=True,
)
]
def lese_monate(sheet):
headers = next(sheet.iter_rows(1, 1, 1, 100, values_only=True))
return next(
sheet.iter_rows(
97,
97,
headers.rindex("Änderungsdatum") + 1,
headers.rindex("letzte") + 1,
values_only=True,
)
)
def lade_vertraege(filename):
with closing(load_workbook(filename)) as workbook:
sheet = workbook["Vertraege"]
column_index = column_index_from_string("R")
return (
lese_vertraege(sheet),
lese_monate(sheet),
list(
sheet.iter_cols(
column_index, column_index, 54, 58, values_only=True
)
),
)
def summiere_vertraege(vertraege, datum, gruppe, rentenzeichen):
summe = 0
summe_renten = 0
for vertrag in vertraege:
if vertrag.ist_faellig_am(datum) and vertrag.gruppe == gruppe:
summe += vertrag.betrag
if vertrag.rentenzeichen == rentenzeichen:
summe_renten += vertrag.betrag
return summe, summe_renten
def main():
vertraege, monate, gruppen = lade_vertraege(
R"C:\Users\Jan\Desktop\Python\python.xlsx"
)
sheet = com.Dispatch("Excel.Application").ActiveSheet
rentenzeichen = sheet.Range("L71").Value
neue_daten = []
renten_gesamt = []
for datum in monate:
summen = []
renten_monat = []
for gruppe in gruppen:
summe, summe_renten = summiere_vertraege(
vertraege, datum, gruppe, rentenzeichen
)
summen.append(summe)
renten_monat.append(summe_renten)
neue_daten.append(summen)
renten_gesamt.append(renten_monat)
sheet.Range("A2:A6").Value = gruppen
sheet.Range(sheet.Range("S2").Value).Value = np.array(neue_daten).T
sheet.Range("B8:BI8").Value = np.array(renten_gesamt).T
if __name__ == "__main__":
main()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Hi Sirius3,
vielen Dank für Deine Mühe. Deine Hilfe ist sehr wertvoll - So lerne ich tatsächlich sehr viel.
Code sieht sehr aufgeräumt aus! Gibt es eine Konvention re/Reihenfolge der Module + Funktionen? Ich hätte gedacht es startet mit "if __name...", dann die Hauptfunktion "main" und dann der Rest, sozusagen von oben nach unten...
Der Code läuft leider nicht durch, sondern bleibt in Zeile 31 mit folgendem Text hängen. ()Ein paar kleine Tipper bei den Variablennamen waren drin, die habe ich korrigiert, d.h. daran liegt es nicht:
Traceback (most recent call last):
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 91, in <module>
main()
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 62, in main
vertraege = lese_vertraege(ws)
^^^^^^^^^^^^^^^^^^
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 31, in lese_vertraege
monat = vertrag_ab.month
^^^^^^^^^^^^^^^^
AttributeError: 'Cell' object has no attribute 'month'. Did you mean: 'font'?
Liegt das daran, dass Vertrag_ab keinen Wert hat oder kein Datum ist? Musst Du denn bei den Definitionen in Zeilen 21 bis 23 nicht hinten ein .value anhängen?
Und Verständnisfragen zu Deinen wertvollen grundsätzlichen Hinweisen:
- wenn keine (print)strings mit "+" - wie stellst Du dann Text und variablen zusammen?
- Warum nutzt Du das (nicht sprechende= "l71" statt des "rentenzeichen"s?
- Warum holst Du die Gruppen, Verträge und Monate (Definition der drei arrays) am Anfang von main (Zeilen 62-67) über load_wb und nicht direkt aus der geöffneten Datei, die das Skript aufruft? Wäre das nicht schneller?
Hoffe alles nachvollziehbar
vielen Dank für Deine Mühe. Deine Hilfe ist sehr wertvoll - So lerne ich tatsächlich sehr viel.
Code sieht sehr aufgeräumt aus! Gibt es eine Konvention re/Reihenfolge der Module + Funktionen? Ich hätte gedacht es startet mit "if __name...", dann die Hauptfunktion "main" und dann der Rest, sozusagen von oben nach unten...
Der Code läuft leider nicht durch, sondern bleibt in Zeile 31 mit folgendem Text hängen. ()Ein paar kleine Tipper bei den Variablennamen waren drin, die habe ich korrigiert, d.h. daran liegt es nicht:
Traceback (most recent call last):
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 91, in <module>
main()
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 62, in main
vertraege = lese_vertraege(ws)
^^^^^^^^^^^^^^^^^^
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 31, in lese_vertraege
monat = vertrag_ab.month
^^^^^^^^^^^^^^^^
AttributeError: 'Cell' object has no attribute 'month'. Did you mean: 'font'?
Liegt das daran, dass Vertrag_ab keinen Wert hat oder kein Datum ist? Musst Du denn bei den Definitionen in Zeilen 21 bis 23 nicht hinten ein .value anhängen?
Und Verständnisfragen zu Deinen wertvollen grundsätzlichen Hinweisen:
- wenn keine (print)strings mit "+" - wie stellst Du dann Text und variablen zusammen?
- Warum nutzt Du das (nicht sprechende= "l71" statt des "rentenzeichen"s?
- Warum holst Du die Gruppen, Verträge und Monate (Definition der drei arrays) am Anfang von main (Zeilen 62-67) über load_wb und nicht direkt aus der geöffneten Datei, die das Skript aufruft? Wäre das nicht schneller?
Hoffe alles nachvollziehbar
- __blackjack__
- User
- Beiträge: 14028
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@JaSyMa: Die Reihenfolge kann nicht erst ``if __name__ …`` und dann die Definition Hauptfunktion sein, weil auch das auf Modulebene Code ist, der ausgeführt wird. Und `main()` kann man erst dann aufrufen wenn ``def main(): …`` ausgeführt wurde, weil dann erst der Name `main` existiert und an eine Funktion gebunden ist.
Dementsprechend machen die meisten das so, dass Sachen die von einer Funktion/Methode zu deren Laufzeit benötigt werden, im Modul ”örtlich” davor definiert werden. Weil das in bestimmten Fällen zwingend notwendig ist, und es sinnvoll ist das nicht wild zu mischen, sondern konsistent immer in dieser Reihenfolge zu machen.
Die Ausnahme liegt am fehlenden `value`-Zugriff. In meiner Überarbeitung habe ich da hoffentlich immer dran gedacht, beziehungsweise die entsprechenden ``values_only=True`` bei den `iter_*()`-Methoden angegeben. Sonst hat man nämlich `Cell`-Objekte. Die haben neben `value` beispielsweise tatsächlich auch ein `font`-Attribut.
Die erste Verständnisfrage ist doch im Quelltext beantwortet: f-Zeichenkettenliterale. Also beispielsweise statt ``"foo" + str(bar)`` besser ``f"foo{bar}"``. Falls auch die Vorlage variabel sein muss, dann die `format()`-Methode auf Zeichenketten. Also ``template = "foo{0}"`` und später dann ``template.format(bar)``.
Die letzte Verständnisfrage verstehe ich nicht: das macht Dein Code doch nicht anders. Heisst das es gibt nur *eine* Exceldatei auf die über zwei verschiedene Wege zugegriffen wird?
Dementsprechend machen die meisten das so, dass Sachen die von einer Funktion/Methode zu deren Laufzeit benötigt werden, im Modul ”örtlich” davor definiert werden. Weil das in bestimmten Fällen zwingend notwendig ist, und es sinnvoll ist das nicht wild zu mischen, sondern konsistent immer in dieser Reihenfolge zu machen.
Die Ausnahme liegt am fehlenden `value`-Zugriff. In meiner Überarbeitung habe ich da hoffentlich immer dran gedacht, beziehungsweise die entsprechenden ``values_only=True`` bei den `iter_*()`-Methoden angegeben. Sonst hat man nämlich `Cell`-Objekte. Die haben neben `value` beispielsweise tatsächlich auch ein `font`-Attribut.
Die erste Verständnisfrage ist doch im Quelltext beantwortet: f-Zeichenkettenliterale. Also beispielsweise statt ``"foo" + str(bar)`` besser ``f"foo{bar}"``. Falls auch die Vorlage variabel sein muss, dann die `format()`-Methode auf Zeichenketten. Also ``template = "foo{0}"`` und später dann ``template.format(bar)``.
Die letzte Verständnisfrage verstehe ich nicht: das macht Dein Code doch nicht anders. Heisst das es gibt nur *eine* Exceldatei auf die über zwei verschiedene Wege zugegriffen wird?
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Vielen Dank für die guten Infos, BlackJack.
Ich habe gerade versucht, mal Deinen code durchlaufen zu lassen. Da ich jede von Euch neu genutzte Bibliothek erst installieren muss habe ich dies mit "pip install attr" und "pip install contextlib" versucht.
Erstere hat installiert, aber beim Codedurchlauf kommt der Fehler "Traceback (most recent call last):
File "C:\Users\Jan\Desktop\Python\Vertraege_BlackJack.py", line 6, in <module>
from attr import attrib, attrs
ImportError: cannot import name 'attrib' from 'attr' (C:\Python311\Lib\site-packages\attr.py)"
Beim Versuch der Installation von contextlib kam zunächst der Hinweis, dass meine Pythonversion nicht neu genug sei. Diese habe ich dann upgedatet, aber die Bibliothek liess sich trotzdem nicht installieren.
C:\Windows\System32>pip install contextlib
ERROR: Could not find a version that satisfies the requirement contextlib (from versions: none)
ERROR: No matching distribution found for contextlib
[notice] A new release of pip available: 22.3.1 -> 23.2.1
[notice] To update, run: python.exe -m pip install --upgrade pip
C:\Windows\System32>python.exe -m pip install --upgrade pip
Requirement already satisfied: pip in c:\python311\lib\site-packages (22.3.1)
Collecting pip
Using cached pip-23.2.1-py3-none-any.whl (2.1 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.3.1
Uninstalling pip-22.3.1:
Successfully uninstalled pip-22.3.1
Successfully installed pip-23.2.1
C:\Windows\System32>pip install contextlib
ERROR: Could not find a version that satisfies the requirement contextlib (from versions: none)
ERROR: No matching distribution found for contextlib
Was muss ich tun, damit ich Deinen code testen kann? Gibt es eine Möglichkeit, mit einem Schub alle relevanten (Basis)Bibliotheken zu installieren und dann nur noch um "Sonderfälle" zu ergänzen. Ich hatte mal gelesen, dass Anaconda das machen würde war nur nicht sicher, ob es irgendeinen Nachteil hat so viele potentiell (für meine cases) unnütze Bibliotheken installiert zu haben. Jetzt habe ich aber das Gefühl, dass mir das Basissetup fehlt, weil ich mit jeder Iteration neue Bibliotheken installieren muss...
Ja, das ist tatsächlich so. Die Datei mit den Quelldaten + Zieldatei für die Berechnungen ist diesselbe, die per VBA Code den Python Ablauf triggert. In meinem Beispielcase ist das python.xlsm.__blackjack__ hat geschrieben: Freitag 8. September 2023, 13:49 Die letzte Verständnisfrage verstehe ich nicht: das macht Dein Code doch nicht anders. Heisst das es gibt nur *eine* Exceldatei auf die über zwei verschiedene Wege zugegriffen wird?
Ich habe gerade versucht, mal Deinen code durchlaufen zu lassen. Da ich jede von Euch neu genutzte Bibliothek erst installieren muss habe ich dies mit "pip install attr" und "pip install contextlib" versucht.
Erstere hat installiert, aber beim Codedurchlauf kommt der Fehler "Traceback (most recent call last):
File "C:\Users\Jan\Desktop\Python\Vertraege_BlackJack.py", line 6, in <module>
from attr import attrib, attrs
ImportError: cannot import name 'attrib' from 'attr' (C:\Python311\Lib\site-packages\attr.py)"
Beim Versuch der Installation von contextlib kam zunächst der Hinweis, dass meine Pythonversion nicht neu genug sei. Diese habe ich dann upgedatet, aber die Bibliothek liess sich trotzdem nicht installieren.
C:\Windows\System32>pip install contextlib
ERROR: Could not find a version that satisfies the requirement contextlib (from versions: none)
ERROR: No matching distribution found for contextlib
[notice] A new release of pip available: 22.3.1 -> 23.2.1
[notice] To update, run: python.exe -m pip install --upgrade pip
C:\Windows\System32>python.exe -m pip install --upgrade pip
Requirement already satisfied: pip in c:\python311\lib\site-packages (22.3.1)
Collecting pip
Using cached pip-23.2.1-py3-none-any.whl (2.1 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.3.1
Uninstalling pip-22.3.1:
Successfully uninstalled pip-22.3.1
Successfully installed pip-23.2.1
C:\Windows\System32>pip install contextlib
ERROR: Could not find a version that satisfies the requirement contextlib (from versions: none)
ERROR: No matching distribution found for contextlib
Was muss ich tun, damit ich Deinen code testen kann? Gibt es eine Möglichkeit, mit einem Schub alle relevanten (Basis)Bibliotheken zu installieren und dann nur noch um "Sonderfälle" zu ergänzen. Ich hatte mal gelesen, dass Anaconda das machen würde war nur nicht sicher, ob es irgendeinen Nachteil hat so viele potentiell (für meine cases) unnütze Bibliotheken installiert zu haben. Jetzt habe ich aber das Gefühl, dass mir das Basissetup fehlt, weil ich mit jeder Iteration neue Bibliotheken installieren muss...
Ich habe es mal mit der Ergänzung ".value" versucht, aber das war nicht die Lösung. Was habe ich falsch gemacht/verstanden?__blackjack__ hat geschrieben: Freitag 8. September 2023, 13:49 Die Ausnahme liegt am fehlenden `value`-Zugriff. In meiner Überarbeitung habe ich da hoffentlich immer dran gedacht, beziehungsweise die entsprechenden ``values_only=True`` bei den `iter_*()`-Methoden angegeben. Sonst hat man nämlich `Cell`-Objekte. Die haben neben `value` beispielsweise tatsächlich auch ein `font`-Attribut.
Traceback (most recent call last):
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 91, in <module>
main()
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 62, in main
vertraege = lese_vertraege(ws)
^^^^^^^^^^^^^^^^^^
File "C:\Users\Jan\Desktop\Python\Vertraege_improved.py", line 32, in lese_vertraege
zahltermine_pro_jahr = [f"Q{row}"].value
^^^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'value'
Was hab ich denn da für Quatsch geschrieben.
An vielen Stellen muss es natürlich so aussehen:
Aber wenn das deine aktuelle Exceldatei ist, solltest du eh den ganzen openpyxl-Code durch die entsprechenden COM-Aufrufe ersetzen.
An vielen Stellen muss es natürlich so aussehen:
Code: Alles auswählen
ws[f"J{row}"].value
- __blackjack__
- User
- Beiträge: 14028
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@JaSyMa: Das PyPI-Package für das Python-Package `attr` heisst leider `attrs`. Und `contextlib` muss man nicht nachinstallieren, das ist Bestandteil der Python-Standardbibliothek. Und zwar schon in Python 2, das heisst ein Python ohne, kannst Du gar nicht haben.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.