Schreiben in eine geöffnete Excel Datei

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.
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

Sirius3 hat geschrieben: Freitag 8. September 2023, 14:52 Was hab ich denn da für Quatsch geschrieben.
An vielen Stellen muss es natürlich so aussehen:

Code: Alles auswählen

ws[f"J{row}"].value
Aber wenn das deine aktuelle Exceldatei ist, solltest du eh den ganzen openpyxl-Code durch die entsprechenden COM-Aufrufe ersetzen.
Dein skript läuft jetzt durch. Merkwürdig ist, dass die Liste "gruppen" sauber gesetzt wird, aber mit Zeile 87 (sheet.Range("R54:R57").Value = gruppen) nicht sauber überträgt. Dort steht dann in allen vier Zellen immer die gleiche Gruppe (erste der Liste). Stimmt der code in Zeile 87 nicht?

Zudem ist mir grad aufgefallen, dass ich beim Versuch eine Zelle mit .value auszulesen, die eine Formel enthält nicht den angezeigten Wert (Ergebnis der Formel), sondern die Formel selbst bekomme. Mit welchem Parameter muss ich abrufen, um den berechneten Wert und nicht die Formel zu bekommen?
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

__blackjack__ hat geschrieben: Freitag 8. September 2023, 15:42 @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.
ich habe attr wieder deinstalliert und attrs installiert. Dafür habe ich jetzt wenigstens das aktuellste python drauf 8)

Folgende Fehler kamen:

Zeilen 68/69 - kein Attribut rindex - habe ich auf Attribut index geändert
Zeile 26 - "datum" hat kein Attribut "month". Datum habe ich mir dann anzeigen lassen - das ist "Renten-zeichen". Ich habe versucht herauszufinden w/wie Du Datum definierst, aber der Aufruf "ist_faellig_am" erwartet die Parameter self und datum, aber Du übergibst soweit ich sehe nur datum. Mehr habe ich leider nicht sehen können. Hast Du eine Idee?
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

Sirius3 hat geschrieben: Freitag 8. September 2023, 14:52 Aber wenn das deine aktuelle Exceldatei ist, solltest du eh den ganzen openpyxl-Code durch die entsprechenden COM-Aufrufe ersetzen.
Ja, das würde ich gern, zumal die (richtige) Quelldatei ein binäres Format hat und soweit ich weiß kann openpyxl damit nicht umgehen.

Muss ich dann alle Zeilen wie ws[f"R{row}"].value in sheet.Range(f"R{row}".value ändern?

Analog ws.cell(row=97, column=month_col).value in sheet.cell((row=97, column=month_col).value?
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Nein, Excel kennt kein cell, nur Range.
Bei der Zuweisung nehrerer Werte wird wahrscheinlich immer eine Liste von Listen erwartet.
Wenn in einer Zelle der String "Renten-zeichen" steht, dann greifst Du wohl auf die falsche Zelle zu.
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

Im (Excel) VBA arbeite ich immer alternativ mit ws.range (absolute Zellenadressierung - Spalte als Buchstabe) vs ws.cells (relative Zellenadressierung - Spalte als Zahl). Insofern habe ich angenommen, dass es diese zwei unterschiedlichen Methoden vielleicht auch in Python gibt. Da ich hier durch eine Reihe iteriere weiß ich nicht wie ich die Variable Spalte (weil Range einen Buchstaben braucht) abbilden soll. Mit ws.cells zähle ich einfach die Spalte in der Schleife hoch. Wenn das hier nicht geht - hast Du ein Beispiel wie Du das mit der “Liste von Listen” meinst?

Und das Beispiel ws[f"R{row}"].value in sheet.Range(f"R{row}".value passt?

Re/Renten-Zeichen: Wo das in Black-Jacks Code herkommt habe ich gefunden - in Zeile 62ff iteriert er durch die Monate der Datei beginnend ab der Spalte mit dem gefundenen tag “Änderungsdatum” (in der Spalte steht “Renten-Zeichen”). Richtig wäre aber die Iteration erst ab der folgenden Spalte. Deshalb steht in Zeile 68 des Codes “gefundene Spalte +1. Warum iteriert der Code nicht ab +1, sondern nimmt als erste Spalte die Spalte mit dem tag?
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Stimmt, es gibt `Cells` mit s. Du mußt beachten, dass Excel von 1 ab zählt, in Python wird aber von 0 gezählt.
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

JaSyMa: `index()` statt `rindex()` geht auch. Das macht dann zwar etwas leicht unterschiedliches als Deine Schleifen, aber wenn man davon ausgeht, dass die beiden Suchbegriffe jeweils nur ein mal vorkommen, ist das Ergebnis das gleiche.

Das mit Startindex (0 vs. 1) bedeutet das man da 2 addieren muss. Dann würde das so aussehen:

Code: Alles auswählen

def lese_monate(sheet):
    headers = next(sheet.iter_rows(1, 1, 1, 100, values_only=True))
    return next(
        sheet.iter_rows(
            97,
            97,
            headers.index("Änderungsdatum") + 2,
            headers.index("letzte") + 2,
            values_only=True,
        )
    )
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

__blackjack__ hat geschrieben: Samstag 9. September 2023, 14:53 JaSyMa: `index()` statt `rindex()` geht auch. Das macht dann zwar etwas leicht unterschiedliches als Deine Schleifen, aber wenn man davon ausgeht, dass die beiden Suchbegriffe jeweils nur ein mal vorkommen, ist das Ergebnis das gleiche.
Ich würde es ja gern so lassen wie von Dir gemacht (weil ganz sicher die bessere Variante), aber rindex wirft den zitierten Fehler...

Ansonsten läuft Dein code jetzt durch generiert aber nur Nullwerte. Ich habe auch gefunden, woran das liegt weiß aber nicht wie ich es abstellen soll. Beim Vergleich der Gruppe pro Durchlauf hast Du nicht eine einzelne Gruppe, sondern immer alle 4. Dadurch stimmt beim Summieren nie gruppe == vertrag.gruppe und es wird nichts summiert.

Ich habe mir bei beiden codes das array "gruppen" listen lassen.

Bei Sirius sieht es so aus

['Mieten und Pachten', 'Leasingkosten (bewegliche Güter)', 'Reparaturen und Instandhaltung', 'Lizenzgebühren']

und bei Dir, Blackjack

[('Mieten und Pachten', 'Leasingkosten (bewegliche Güter)', 'Reparaturen und Instandhaltung', 'Lizenzgebühren')]

Nur zwei kleine Klammern, aber die bedingen, dass es nicht funktioniert. Bei "for gruppe in gruppen" ist jede gruppe das komplette array. Zusammengestellt wird die Liste in der Funktion lade_vertraege, aber ich weiß nicht wie ich es ändern soll, um den Vergleich sauber zu haben.

Zudem werden die Gruppen in der drittletzetn Zeile von main (sheet.Range("R54:R57").Value = gruppen) nicht reingeschrieben, sondern in alle Zellen immer nur die erste Gruppe.

Hast Du Ideen, wie ich den letzten meter ändern muss, um Deinen code lauffähig zu kriegen, BlackJack?
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

Sirius3 hat geschrieben: Samstag 9. September 2023, 13:08 Stimmt, es gibt `Cells` mit s. Du mußt beachten, dass Excel von 1 ab zählt, in Python wird aber von 0 gezählt.
.cells scheint nicht zu funktionieren. Das habe ich probiert:

import win32com.client as com

excel = com.Dispatch('Excel.Application')
sheet = excel.ActiveSheet

rentenzeichen = sheet.Range("L71").Value
print(rentenzeichen)

rentenzeichen1 = sheet.cells(row=71, column=11).value
print(rentenzeichen1)

Er meckert zwar nicht, dass es die Methode nicht gäbe, dafür aber in der Zeile mit der Definition des rentenzeichens1^:

Traceback (most recent call last):
File "c:\Users\Jan\Desktop\Python\test_csv.py", line 9, in <module>
rentenzeichen1 = sheet.cells(row=71, column=11).value

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: CDispatch.__call__() got an unexpected keyword argument 'row'

Habt Ihr eine Idee?
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Es hilft ja nichts, wenn Du hier irgendwelchen Code kopierst, den Du nicht verstehst.
Das mit den Gruppen hatte ich Dir doch schon erklärt. Das sollte also einfach sein, das zu reparieren.
Und natürlich versteht die COM-Schnittstelle Deine Keyword-Argumente nicht.
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

Mir hilft es eine Menge, wenn ich Eure Codebeispiele (die ich zu dem Zeitpunkt natürlich noch nicht in Gänze verstehe) kopiere. Ich nutze die nicht nur, um das originäre Problem zu lösen, sondern versuche jede Codezeile zu verstehen, damit ich sie beim nächsten Case potentiell adaptieren und selbst anwenden kann. Und natürlich verstehe ich dann auch noch nicht alles - dann frage ich a den speziellen Punkten nach und dank Eures Feedbacks habe ich am Ende den gesamten Code verstanden. Wenn Du eine besser “Lernmethode hast gern…
Das mit den Gruppen hatte ich Dir doch schon erklärt. Das sollte also einfach sein, das zu reparieren.
Meinst Du das als Erklärung für die Gruppen. Dazu hatte ich Dich nach einem Beispiel gefragt, da ich nicht verstanden habe wie ich diesen Hinweis umsetzen soll. Insofern mit meinem aktuellen Kenntnisstand leider noch nicht einfach zu reparieren…
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Ein Range hat Zeilen und Spalten, und diese Struktur müssen die übergebenen Listen auch haben.
In Deinem Fall also eine Liste mit jeweils Listen, die einen Eintrag für die eine Spalte haben.

Code: Alles auswählen

sheet.Range("A2:A6").Value = [[g] for g in gruppen]
karolus
User
Beiträge: 144
Registriert: Samstag 22. August 2009, 22:34

Sirius3 hat geschrieben: Sonntag 10. September 2023, 10:36 Ein Range hat Zeilen und Spalten, und diese Struktur müssen die übergebenen Listen auch haben.
In Deinem Fall also eine Liste mit jeweils Listen, die einen Eintrag für die eine Spalte haben.

Code: Alles auswählen

sheet.Range("A2:A6").Value = [[g] for g in gruppen]
[off topic:]nur mal so aus Interesse:
In LibreOffice würde jetzt funktionieren:

Code: Alles auswählen

sheet["A2:A6"].DataArray = list(zip(gruppen))
ich meine jetzt nur ``list(zip(gruppen))`` was ja eine Liste von tuple'n zurückgibt.
geht letzteres auch hier oder brauchts explizit eine Liste von Liste[n] ?
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

JaSyMa hat geschrieben: Samstag 9. September 2023, 17:41
__blackjack__ hat geschrieben: Samstag 9. September 2023, 14:53 JaSyMa: `index()` statt `rindex()` geht auch. Das macht dann zwar etwas leicht unterschiedliches als Deine Schleifen, aber wenn man davon ausgeht, dass die beiden Suchbegriffe jeweils nur ein mal vorkommen, ist das Ergebnis das gleiche.
Ich würde es ja gern so lassen wie von Dir gemacht (weil ganz sicher die bessere Variante), aber rindex wirft den zitierten Fehler...

Ansonsten läuft Dein code jetzt durch generiert aber nur Nullwerte. Ich habe auch gefunden, woran das liegt weiß aber nicht wie ich es abstellen soll. Beim Vergleich der Gruppe pro Durchlauf hast Du nicht eine einzelne Gruppe, sondern immer alle 4. Dadurch stimmt beim Summieren nie gruppe == vertrag.gruppe und es wird nichts summiert.

Ich habe mir bei beiden codes das array "gruppen" listen lassen.

Bei Sirius sieht es so aus

['Mieten und Pachten', 'Leasingkosten (bewegliche Güter)', 'Reparaturen und Instandhaltung', 'Lizenzgebühren']

und bei Dir, Blackjack

[('Mieten und Pachten', 'Leasingkosten (bewegliche Güter)', 'Reparaturen und Instandhaltung', 'Lizenzgebühren')]

Nur zwei kleine Klammern, aber die bedingen, dass es nicht funktioniert. Bei "for gruppe in gruppen" ist jede gruppe das komplette array. Zusammengestellt wird die Liste in der Funktion lade_vertraege, aber ich weiß nicht wie ich es ändern soll, um den Vergleich sauber zu haben.
@BlackJack, kannst Du hier bitte mal bei Deiner Liste schauen? Würde Deine Codealternative gern einmal richtig durchlaufen lassen, aber kriege es mit Deiner Gruppenliste nicht hin :cry:
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Wo hast Du denn jetzt noch konkret ein Problem?
Wie sieht Dein Code inzwischen aus?

Das ganze müßte inzwischen ungefähr so aussehen:

Code: Alles auswählen

import win32com.client as com
from attr import attrib, attrs

@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.Range("J98:R122").Value
    ]


def lese_monate(sheet):
    headers = sheet.Range("A1:CV1")
    first_cell = headers.Find("Änderungsdatum").Address
    last_cell = headers.Find("letzte").Address
    return sheet.Range(f"{first_cell}:{last_cell}".replace("1", "97")).Value[0]


def lese_gruppen(sheet):
    return [g for (g,) in sheet.Range("R54:R57").Value]


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():
    excel = com.Dispatch("Excel.Application")
    sheet = excel.ActiveWorkbook.Sheets["Vertraege"]
    vertraege = lese_vertraege(sheet)
    monate = lese_monate(sheet)
    gruppen = lese_gruppen(sheet)

    sheet = excel.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 = [(g,) for g in gruppen]
    sheet.Range(sheet.Range("S2").Value).Value = zip(*neue_daten)
    sheet.Range("B8:BI8").Value = zip(*renten_gesamt)


if __name__ == "__main__":
    main()
JaSyMa
User
Beiträge: 30
Registriert: Sonntag 3. September 2023, 10:52

@Sirius3, habe ein paar Parameter in Deinem neuen auf BlackJacks basierendem code angepasst und nun läuft er durch. Ich musste numpy wieder hinzunehmen, weil er zip-Objekte nicht in com Umgebungen konvertieren kann. Code ist jetzt mit Faktor 3 schneller im Vergleich zu Deiner originären Variante. Ich vermute, dass ist viel performanter, weil die Infos direkt aus der geöffneten Datei (ohne Openpyxl) geholt werden. ist jetzt wirklich sehr performant!

Hier noch der letzte code von BlackJack.

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
import time

zeit_anf = time.time()

@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.month

    def ist_faellig_am(self, datum):
        return (
            (datum.month + 12 - self.monat) % (12/self.zahltermine_pro_jahr) == 0
            and (not self.ab or 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,
            121,
            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.index("Änderungsdatum") + 2,
            headers.index("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, 57, 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.xlsm"
    )

    sheet = com.Dispatch("Excel.Application").ActiveSheet
    rentenzeichen = sheet.Range("L71").Value

    neue_daten = []
    renten_gesamt = []
    for datum in monate:
        summen = []
        renten_monat = []
        #print(gruppen)
        for gruppe in gruppen:
            #print(gruppe)
            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)

    # data_rows in den ausgewählten Bereich schreiben:
    #sheet.Range("R54:R57").Value = [[g] for g in gruppen]
    sheet.Range(sheet.Range("S2").Value).Value = np.array(neue_daten).T
    sheet.Range("U63:CB63").Value = np.array(renten_gesamt).T

    zeit_end = time.time()
    sheet.Range("Q47").Value = zeit_end - zeit_anf
    print (zeit_end - zeit_anf)

if __name__ == "__main__":
    main()
Ich hatte ihn noch nicht auf die Version ohne openpyxl angepasst, da ich erst sicherstellen wollte, dass er saubere Werte generiert. Der entscheidende Unterschied zu Deiner Variante ist die Generierung der Gruppen. Blackjack erstellt in lade_vertraege eine Liste, die sich im output durch eine zusätzliche öffnende / schliessende (Rund)klammer von Deiner Gruppenliste unterscheidet. Und dadurch wird wie beschrieben beim Schleifendurchlauf nicht gegen eine Gruppe aus der Liste , sondern immer gegen die gesamte Liste gematcht, was bewirkt, dass nie die Bedingung "Gruppe des Schleifendurchlaufs == Gruppe" passt. Damit läuft Blackjacks code zwar sauber und schnell durch, aber generiert nur Nullwerte (weil keine erfüllte Bedingung).

Mich interessiert der Unterschied eigentlich nur noch zum Lernen (warum klappt es mit der einen Methode und der anderen nicht?). Prozessual bin ich mit dem aktuellen Stand sehr zufrieden.
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Eine Liste mit Tuplen ist etwas anderes als eine Liste mit den Werte direkt. Da Ranges in Worksheets Matrizen sind, liegen die eben in Listen von Tupeln vor, und für die Verarbeitung braucht man aber eine einfache Liste; deshalb braucht es Code zum Konvertieren.
TenchiMuyo1984
User
Beiträge: 18
Registriert: Donnerstag 17. Januar 2019, 21:17

Ich nutze für diesen Zweck das Modul xlwings.
Mit diesem kann man mit geöffneten Excel-Dokumente interagieren.

Code: Alles auswählen

import xlwings as xw

# Pfad zum Excel-Dokument
s_file_name = r"D:\Python\test.xlsx"

# definiert das zu ladende Excel-Dokument
wb = xw.Book(s_file_name)

# wählt ein Tabellenblatt aus. In diesem Fall das erste.
sht = wb.sheets[0]

# Schreibt in die Zelle B3 (Zeile 3 und Spalte 2) den Wert (value) 42
sht.cells(3, 2).value = 42
Hierzu öffne ich die Excel-Datei mit Excel und führe das Programm aus.

Viel Spass damit
Antworten