mitmario.dev

Die Monatsübersicht

Python Sandbox 4 Min Lesezeit 4 BeispieleLektion 4 von 8

Meistens will man von einem Kontoauszug etwas über einen Monat wissen, etwa wohin das Geld im September gegangen ist. Eingelesen und eingeordnet ist inzwischen alles. Nur läuft der Auszug über drei Monate. Für die Antwort muss das Werkzeug also aussuchen, zusammenzählen und das Ergebnis ordentlich hinschreiben.

Zusammenzählen nach einer Kategorie kennst du aus Lektion 7.5, eine Tabelle mit tabulate aus Abschnitt 13. Neu ist, dass zwei Merkmale zusammenkommen und dass Geld in beide Richtungen fließt. Die Beispiele nehmen dafür das Haushaltsbuch, diesmal über ein ganzes Jahr.

Zwei Merkmale, ein Schlüssel

Nach Monat und Kategorie
from haushalt import EINTRAEGE

summen = {}
for datum, kategorie, betrag in EINTRAEGE:
    schluessel = (datum.strftime("%Y-%m"), kategorie)
    summen[schluessel] = summen.get(schluessel, 0) + betrag

for schluessel, betrag in sorted(summen.items()):
    monat, kategorie = schluessel
    print(monat, kategorie, betrag)

Gruppiert wird wie beim Zusammenzählen nach einer Kategorie. Nur besteht der Schlüssel jetzt aus zwei Teilen, dem Monat und der Kategorie.

Ein Tupel wie ("2026-09", "Lebensmittel") darf Schlüssel eines Dictionarys sein, weil es sich anders als eine Liste nicht ändern lässt. Und es sortiert sich von selbst erst nach dem Monat und dann nach der Kategorie. In der Schleife kommen die beiden Teile mit monat, kategorie = schluessel wieder aus dem Tupel heraus.

Der Monat kommt aus dem Datum, mit strftime aus Lektion 10.4, diesmal mit "%Y-%m". Vorn steht also das Jahr und dann der Monat mit zwei Stellen. So wird er auch als Text richtig sortiert, aus demselben Grund wie das Datum mit dem Jahr vorn, das in Abschnitt 16 in SQLite stand.

Der Monat braucht sein Jahr

Ein September zu viel
from haushalt import EINTRAEGE

summen = {}
for datum, kategorie, betrag in EINTRAEGE:
    schluessel = (datum.month, kategorie)
    summen[schluessel] = summen.get(schluessel, 0) + betrag

for schluessel, betrag in sorted(summen.items()):
    monat, kategorie = schluessel
    print(monat, kategorie, betrag)

Dieser Fehler fällt erst auf, wenn die Daten über einen Jahreswechsel gehen. datum.month ist für den September 2025 und den September 2026 dieselbe 9, und schon stehen die Lebensmittel zweier Jahre zusammen in einer Zeile: -126.35 statt -78.15.

Im Programm sieht nichts falsch aus. Wer nur die Daten eines Jahres hat, merkt es nie. Wer zwei hat, wundert sich über einen ziemlich teuren September.

Ein Kontoauszug geht oft über den Jahreswechsel, und spätestens wenn du dir die Exporte eines Jahres zusammenkopierst, hast du jeden Monat zweimal. Mit dem Jahr im Schlüssel kann das nicht passieren. Beim Aussuchen ist es dasselbe. Gefragt wird, ob der ganze Text "2026-09" passt, nicht nur die 9.

Einnahmen zählen extra

Einnahmen zählen extra
from haushalt import EINTRAEGE

netto = {}
ausgaben = {}
einnahmen = 0
for datum, kategorie, betrag in EINTRAEGE:
    if datum.strftime("%Y-%m") != "2026-09":
        continue
    netto[kategorie] = netto.get(kategorie, 0) - betrag
    if betrag < 0:
        ausgaben[kategorie] = ausgaben.get(kategorie, 0) - betrag
    else:
        einnahmen += betrag

print("Alles zusammengezählt:", netto)
print("Nur die Ausgaben:", ausgaben)
print("Dazu Einnahmen:", einnahmen)

Im September 2026 hat Ole 15 Euro für seine Kinokarte zurückgezahlt, gebucht unter Freizeit. Wenn man alle Beträge einer Kategorie zusammenzählt, stehen bei Freizeit 15 Euro. Das stimmt aber nicht, denn es gingen 30 Euro hinaus und 15 kamen herein.

Für die Frage, wofür das Geld draufging, zählen nur die Ausgaben. Alles, was hereinkam, steht für sich. Am Kontoauszug ist es mit einer Erstattung der Nebenkosten genauso. Wer sie gegen die Miete rechnet, wohnt auf dem Papier billiger als in Wirklichkeit.

Die Trennung kostet ein if. Beträge unter null zählen zu den Ausgaben, alles andere zu den Einnahmen. Die Ausgaben kommen dabei ohne Minus in die Summe, damit in der Übersicht keine negativen Zahlen stehen. Was am Ende bleibt, ist dann einfach Einnahmen minus Ausgaben.

Beträge rechts in der Spalte

Beträge rechts in der Spalte
from decimal import Decimal

from tabulate import tabulate


def euro(betrag):
    """Schreibt einen Betrag deutsch, mit Tausenderpunkt: 1.234,50 €."""
    text = f"{betrag:,.2f}"
    return text.replace(",", "_").replace(".", ",").replace("_", ".") + " €"


zeilen = [
    ["Wohnen", euro(Decimal("1204.00"))],
    ["Lebensmittel", euro(Decimal("78.15"))],
    ["Freizeit", euro(Decimal("30.00"))],
]
print(tabulate(zeilen, headers=["Kategorie", "Ausgaben"], tablefmt="rounded_outline"))
print(tabulate(zeilen, headers=["Kategorie", "Ausgaben"], tablefmt="rounded_outline", colalign=("left", "right")))

In der ersten Tabelle mit tabulate standen die Beträge als Zahlen da, und tabulate hat sie von selbst rechts ausgerichtet. Deutsch geschrieben mit euro() sind sie aber Text, und Text stellt tabulate links. Dann stehen 1.204,00 € und 30,00 € mit dem Anfang untereinander, und die Stellen lassen sich nicht mehr vergleichen.

Mit colalign gibst du für jede Spalte an, wohin sie gehört, "left" oder "right", in der Reihenfolge der Spalten.

Ein Text statt einer Ausgabe

Die Übersicht in bericht.py gibt selbst nichts aus. Sie gibt einen Text zurück, und erst main.py bringt ihn auf den Bildschirm. Das wirkt wie ein Umweg, hat aber zwei Gründe.

Einen Text kann ein Test prüfen, mit in oder ==. Bei einer Ausgabe auf dem Bildschirm geht das nur über Umwege, und in der nächsten Lektion kommen die Tests. Außerdem lässt sich derselbe Text später auch in eine Datei schreiben oder in eine Mail stecken, ohne dass uebersicht davon wissen muss.

Mehrere Zeilen werden mit "\n".join(...) zu einem Text, so wie früher die Teile eines Satzes.

Was die Aufgabe will

In bericht.py wartet uebersicht(buchungen, monat). Die Funktion soll die Buchungen eines Monats aussuchen, die Ausgaben je Kategorie zusammenzählen und die Einnahmen für sich, und dann alles als Text zurückgeben, mit einer Tabelle von tabulate in der Mitte.

Zum Mitnehmen

Zusammengefasst wird nach einem Merkmal oder nach mehreren, und bei mehreren ist der Schlüssel des Dictionarys ein Tupel. Weil ein Monat erst mit seinem Jahr eindeutig ist, steht dort strftime("%Y-%m") und nicht datum.month. Einnahmen und Ausgaben werden getrennt gezählt, damit keine Rückzahlung eine Ausgabe versteckt, und weil tabulate Text links stellt, brauchen deutsche Beträge colalign.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.