Projekt: Kontaktbuch in Python
Erstelle ein vollständiges Kontaktbuch mit CRUD-Operationen, JSON-Speicherung, Validierung und CSV-Export - ein praxisnahes Python-Projekt
Projekt: Kontaktbuch in Python
In diesem Projekt bauen wir ein vollständiges Kontaktbuch, das Kontakte verwalten, in Dateien speichern und exportieren kann. Du lernst dabei, wie man ein reales Programm mit sauberer Architektur entwickelt.
Projektübersicht und Ziele
Unser Kontaktbuch wird folgende Funktionen bieten:
- CRUD-Operationen: Kontakte erstellen, anzeigen, bearbeiten und löschen
- Suche und Filterung: Kontakte nach Name, E-Mail oder Telefon finden
- Persistente Speicherung: Daten in einer JSON-Datei speichern und laden
- Validierung: E-Mail-Adressen und Telefonnummern prüfen
- Import/Export: Kontakte als CSV-Datei importieren und exportieren
- OOP-Struktur: Sauberer Code mit Klassen
Schritt 1: Kontakt-Datenstruktur
Wir definieren zuerst, welche Informationen ein Kontakt haben soll:
# Ein Kontakt als Dictionary
kontakt_beispiel = {
"vorname": "Max",
"nachname": "Mustermann",
"telefon": "0171-1234567",
"email": "max@example.com",
"adresse": "Musterstraße 1, 12345 Berlin",
"notizen": "Kollege aus der Arbeit"
}
Mit einer Funktion können wir neue Kontakte erstellen:
def kontakt_erstellen():
"""Erstellt einen neuen Kontakt durch Benutzereingabe."""
print("\n--- Neuen Kontakt erstellen ---")
vorname = input("Vorname: ").strip()
nachname = input("Nachname: ").strip()
telefon = input("Telefon: ").strip()
email = input("E-Mail: ").strip()
adresse = input("Adresse: ").strip()
notizen = input("Notizen: ").strip()
kontakt = {
"vorname": vorname,
"nachname": nachname,
"telefon": telefon,
"email": email,
"adresse": adresse,
"notizen": notizen
}
return kontakt
Schritt 2: CRUD-Operationen
CRUD steht für Create, Read, Update, Delete — die vier grundlegenden Operationen für Datenverwaltung:
# Unsere Kontaktliste
kontakte = []
# CREATE -- Kontakt hinzufügen
def kontakt_hinzufuegen(kontakt):
"""Fügt einen Kontakt zur Liste hinzu."""
kontakte.append(kontakt)
print(f"\n Kontakt '{kontakt['vorname']} {kontakt['nachname']}' "
f"wurde hinzugefügt.")
# READ -- Alle Kontakte anzeigen
def alle_kontakte_anzeigen():
"""Zeigt alle Kontakte übersichtlich an."""
if not kontakte:
print("\n Keine Kontakte vorhanden.")
return
print(f"\n{'=' * 60}")
print(f" KONTAKTBUCH ({len(kontakte)} Kontakte)")
print(f"{'=' * 60}")
for i, k in enumerate(kontakte, 1):
print(f"\n [{i}] {k['vorname']} {k['nachname']}")
if k["telefon"]:
print(f" Tel: {k['telefon']}")
if k["email"]:
print(f" E-Mail: {k['email']}")
if k["adresse"]:
print(f" Adresse: {k['adresse']}")
if k["notizen"]:
print(f" Notizen: {k['notizen']}")
print(f"\n{'=' * 60}")
# READ -- Einzelnen Kontakt anzeigen
def kontakt_details(index):
"""Zeigt die Details eines einzelnen Kontakts."""
if 0 <= index < len(kontakte):
k = kontakte[index]
print(f"\n{'─' * 40}")
print(f" {k['vorname']} {k['nachname']}")
print(f"{'─' * 40}")
print(f" Telefon: {k['telefon'] or '---'}")
print(f" E-Mail: {k['email'] or '---'}")
print(f" Adresse: {k['adresse'] or '---'}")
print(f" Notizen: {k['notizen'] or '---'}")
print(f"{'─' * 40}")
else:
print(" Ungültiger Index!")
# UPDATE -- Kontakt bearbeiten
def kontakt_bearbeiten(index):
"""Bearbeitet einen bestehenden Kontakt."""
if not (0 <= index < len(kontakte)):
print(" Ungültiger Index!")
return
k = kontakte[index]
print(f"\n--- Kontakt bearbeiten: {k['vorname']} {k['nachname']} ---")
print(" (Leer lassen, um den Wert beizubehalten)")
felder = ["vorname", "nachname", "telefon", "email", "adresse", "notizen"]
bezeichnungen = ["Vorname", "Nachname", "Telefon", "E-Mail", "Adresse", "Notizen"]
for feld, bezeichnung in zip(felder, bezeichnungen):
aktuell = k[feld]
neuer_wert = input(f" {bezeichnung} [{aktuell}]: ").strip()
if neuer_wert:
k[feld] = neuer_wert
print(f"\n Kontakt wurde aktualisiert.")
# DELETE -- Kontakt löschen
def kontakt_loeschen(index):
"""Löscht einen Kontakt nach Bestätigung."""
if not (0 <= index < len(kontakte)):
print(" Ungültiger Index!")
return
k = kontakte[index]
name = f"{k['vorname']} {k['nachname']}"
bestaetigung = input(f" '{name}' wirklich löschen? (j/n): ").strip().lower()
if bestaetigung == "j":
kontakte.pop(index)
print(f" '{name}' wurde gelöscht.")
else:
print(" Löschung abgebrochen.")
Jede CRUD-Operation ist eine eigene Funktion mit klarer Verantwortung.
Schritt 3: Menü-System
Das Menü verbindet alle Funktionen in einer benutzerfreundlichen Schleife:
def kontakt_auswahl(aktion_text="auswählen"):
"""Lässt den Benutzer einen Kontakt per Nummer auswählen."""
if not kontakte:
print("\n Keine Kontakte vorhanden.")
return -1
alle_kontakte_anzeigen()
try:
nummer = int(input(f"\n Kontakt-Nr. zum {aktion_text}: ")) - 1
if 0 <= nummer < len(kontakte):
return nummer
print(" Ungültige Nummer!")
return -1
except ValueError:
print(" Bitte gib eine Zahl ein!")
return -1
def hauptmenue():
"""Zeigt das Hauptmenü und verarbeitet die Eingabe."""
while True:
print(f"\n{'=' * 40}")
print(f" KONTAKTBUCH")
print(f"{'=' * 40}")
print(" 1. Neuen Kontakt erstellen")
print(" 2. Alle Kontakte anzeigen")
print(" 3. Kontakt-Details anzeigen")
print(" 4. Kontakt bearbeiten")
print(" 5. Kontakt löschen")
print(" 6. Kontakt suchen")
print(" 7. Speichern")
print(" 8. Laden")
print(" 9. Als CSV exportieren")
print(" 0. Beenden")
print(f"{'=' * 40}")
auswahl = input("Deine Wahl: ").strip()
if auswahl == "1":
neuer_kontakt = kontakt_erstellen()
kontakt_hinzufuegen(neuer_kontakt)
elif auswahl == "2":
alle_kontakte_anzeigen()
elif auswahl == "3":
idx = kontakt_auswahl("anzeigen")
if idx >= 0:
kontakt_details(idx)
elif auswahl == "4":
idx = kontakt_auswahl("bearbeiten")
if idx >= 0:
kontakt_bearbeiten(idx)
elif auswahl == "5":
idx = kontakt_auswahl("löschen")
if idx >= 0:
kontakt_loeschen(idx)
elif auswahl == "6":
kontakt_suchen()
elif auswahl == "7":
kontakte_speichern()
elif auswahl == "8":
kontakte_laden()
elif auswahl == "9":
als_csv_exportieren()
elif auswahl == "0":
print("\nAuf Wiedersehen!")
break
else:
print(" Ungültige Auswahl!")
Schritt 4: Suche und Filterung
Benutzer sollen Kontakte schnell finden können:
def kontakt_suchen():
"""Sucht Kontakte nach einem Suchbegriff."""
suchbegriff = input("\n Suchbegriff eingeben: ").strip().lower()
if not suchbegriff:
print(" Bitte gib einen Suchbegriff ein.")
return
treffer = []
for i, k in enumerate(kontakte):
# In allen Feldern suchen
durchsuchbar = f"{k['vorname']} {k['nachname']} {k['telefon']} " \
f"{k['email']} {k['adresse']} {k['notizen']}"
if suchbegriff in durchsuchbar.lower():
treffer.append((i, k))
if not treffer:
print(f" Keine Kontakte für '{suchbegriff}' gefunden.")
return
print(f"\n {len(treffer)} Treffer für '{suchbegriff}':")
print(f" {'─' * 45}")
for idx, k in treffer:
print(f" [{idx + 1}] {k['vorname']} {k['nachname']} - "
f"{k['telefon']} - {k['email']}")
Die Suche ist case-insensitive und durchsucht alle Felder des Kontakts. So findet man einen Kontakt sowohl über den Namen als auch über die E-Mail oder Telefonnummer.
Schritt 5: Daten in JSON-Datei speichern und laden
Damit die Kontakte nach dem Programmende erhalten bleiben, speichern wir sie als JSON:
import json
import os
DATEINAME = "kontakte.json"
def kontakte_speichern(dateipfad=DATEINAME):
"""Speichert alle Kontakte in eine JSON-Datei."""
try:
with open(dateipfad, "w", encoding="utf-8") as datei:
json.dump(kontakte, datei, indent=2, ensure_ascii=False)
print(f"\n {len(kontakte)} Kontakte in '{dateipfad}' gespeichert.")
except IOError as e:
print(f"\n Fehler beim Speichern: {e}")
def kontakte_laden(dateipfad=DATEINAME):
"""Lädt Kontakte aus einer JSON-Datei."""
global kontakte
if not os.path.exists(dateipfad):
print(f"\n Datei '{dateipfad}' nicht gefunden.")
return
try:
with open(dateipfad, "r", encoding="utf-8") as datei:
geladene = json.load(datei)
if not isinstance(geladene, list):
print(" Ungültiges Dateiformat!")
return
kontakte = geladene
print(f"\n {len(kontakte)} Kontakte aus '{dateipfad}' geladen.")
except json.JSONDecodeError:
print(" Fehler: Datei enthält ungültiges JSON!")
except IOError as e:
print(f" Fehler beim Laden: {e}")
So sieht die JSON-Datei aus:
[
{
"vorname": "Max",
"nachname": "Mustermann",
"telefon": "0171-1234567",
"email": "max@example.com",
"adresse": "Musterstraße 1, 12345 Berlin",
"notizen": "Kollege"
},
{
"vorname": "Anna",
"nachname": "Schmidt",
"telefon": "0172-9876543",
"email": "anna.schmidt@example.com",
"adresse": "Hauptstraße 42, 80331 München",
"notizen": ""
}
]
Schritt 6: OOP-Refactoring
Jetzt organisieren wir den Code mit Klassen. Eine Contact-Klasse für einzelne Kontakte und eine AddressBook-Klasse für die Verwaltung:
import json
import os
import csv
import re
class Contact:
"""Repräsentiert einen einzelnen Kontakt."""
def __init__(self, vorname, nachname, telefon="", email="",
adresse="", notizen=""):
self.vorname = vorname
self.nachname = nachname
self.telefon = telefon
self.email = email
self.adresse = adresse
self.notizen = notizen
@property
def voller_name(self):
"""Gibt den vollen Namen zurück."""
return f"{self.vorname} {self.nachname}"
def to_dict(self):
"""Konvertiert den Kontakt in ein Dictionary."""
return {
"vorname": self.vorname,
"nachname": self.nachname,
"telefon": self.telefon,
"email": self.email,
"adresse": self.adresse,
"notizen": self.notizen
}
@classmethod
def from_dict(cls, daten):
"""Erstellt einen Kontakt aus einem Dictionary."""
return cls(
vorname=daten.get("vorname", ""),
nachname=daten.get("nachname", ""),
telefon=daten.get("telefon", ""),
email=daten.get("email", ""),
adresse=daten.get("adresse", ""),
notizen=daten.get("notizen", "")
)
def __str__(self):
return f"{self.voller_name} | {self.telefon} | {self.email}"
def __repr__(self):
return f"Contact('{self.vorname}', '{self.nachname}')"
class AddressBook:
"""Verwaltet eine Sammlung von Kontakten."""
def __init__(self, dateipfad="kontakte.json"):
self.kontakte = []
self.dateipfad = dateipfad
# --- CRUD-Operationen ---
def hinzufuegen(self, kontakt):
"""Fügt einen neuen Kontakt hinzu."""
self.kontakte.append(kontakt)
print(f" Kontakt '{kontakt.voller_name}' hinzugefügt.")
def alle_anzeigen(self):
"""Zeigt alle Kontakte an."""
if not self.kontakte:
print("\n Keine Kontakte vorhanden.")
return
print(f"\n{'=' * 60}")
print(f" KONTAKTBUCH ({len(self.kontakte)} Kontakte)")
print(f"{'=' * 60}")
for i, k in enumerate(self.kontakte, 1):
print(f" [{i}] {k.voller_name}")
if k.telefon:
print(f" Tel: {k.telefon}")
if k.email:
print(f" E-Mail: {k.email}")
if k.adresse:
print(f" Adresse: {k.adresse}")
if k.notizen:
print(f" Notizen: {k.notizen}")
print(f"{'=' * 60}")
def details(self, index):
"""Zeigt Details eines Kontakts."""
k = self._kontakt_holen(index)
if k is None:
return
print(f"\n{'─' * 40}")
print(f" {k.voller_name}")
print(f"{'─' * 40}")
print(f" Telefon: {k.telefon or '---'}")
print(f" E-Mail: {k.email or '---'}")
print(f" Adresse: {k.adresse or '---'}")
print(f" Notizen: {k.notizen or '---'}")
print(f"{'─' * 40}")
def bearbeiten(self, index):
"""Bearbeitet einen Kontakt."""
k = self._kontakt_holen(index)
if k is None:
return
print(f"\n--- Bearbeiten: {k.voller_name} ---")
print(" (Leer lassen = Wert beibehalten)")
felder = [
("vorname", "Vorname"),
("nachname", "Nachname"),
("telefon", "Telefon"),
("email", "E-Mail"),
("adresse", "Adresse"),
("notizen", "Notizen"),
]
for attr, label in felder:
aktuell = getattr(k, attr)
neuer_wert = input(f" {label} [{aktuell}]: ").strip()
if neuer_wert:
setattr(k, attr, neuer_wert)
print(f" Kontakt aktualisiert.")
def loeschen(self, index):
"""Löscht einen Kontakt nach Bestätigung."""
k = self._kontakt_holen(index)
if k is None:
return
antwort = input(f" '{k.voller_name}' löschen? (j/n): ").strip().lower()
if antwort == "j":
self.kontakte.pop(index)
print(f" '{k.voller_name}' gelöscht.")
else:
print(" Abgebrochen.")
def _kontakt_holen(self, index):
"""Gibt den Kontakt am Index zurück oder None."""
if 0 <= index < len(self.kontakte):
return self.kontakte[index]
print(" Ungültiger Index!")
return None
# --- Suche ---
def suchen(self, suchbegriff):
"""Durchsucht alle Kontakte nach einem Begriff."""
suchbegriff = suchbegriff.lower()
treffer = []
for i, k in enumerate(self.kontakte):
text = f"{k.voller_name} {k.telefon} {k.email} " \
f"{k.adresse} {k.notizen}".lower()
if suchbegriff in text:
treffer.append((i, k))
return treffer
# --- Dateioperationen ---
def speichern(self):
"""Speichert alle Kontakte als JSON."""
daten = [k.to_dict() for k in self.kontakte]
try:
with open(self.dateipfad, "w", encoding="utf-8") as f:
json.dump(daten, f, indent=2, ensure_ascii=False)
print(f" {len(self.kontakte)} Kontakte gespeichert.")
except IOError as e:
print(f" Fehler beim Speichern: {e}")
def laden(self):
"""Lädt Kontakte aus JSON-Datei."""
if not os.path.exists(self.dateipfad):
print(f" Datei '{self.dateipfad}' nicht gefunden.")
return
try:
with open(self.dateipfad, "r", encoding="utf-8") as f:
daten = json.load(f)
self.kontakte = [Contact.from_dict(d) for d in daten]
print(f" {len(self.kontakte)} Kontakte geladen.")
except (json.JSONDecodeError, IOError) as e:
print(f" Fehler beim Laden: {e}")
# --- Sortierung ---
def sortieren(self, nach="nachname"):
"""Sortiert die Kontakte nach einem Feld."""
self.kontakte.sort(key=lambda k: getattr(k, nach, "").lower())
print(f" Kontakte nach '{nach}' sortiert.")
Die OOP-Version hat klare Vorteile: Jeder Kontakt ist ein Objekt mit eigenen Methoden, und das AddressBook kapselt die gesamte Verwaltungslogik.
Schritt 7: Validierung
Bevor wir Kontakte speichern, sollten wir die Eingaben validieren:
import re
class Validator:
"""Validiert Kontakt-Eingaben."""
@staticmethod
def email_validieren(email):
"""Prüft, ob eine E-Mail-Adresse gültig ist."""
if not email:
return True # Leere E-Mail ist ok (optional)
# Einfaches E-Mail-Muster
muster = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(muster, email):
return True
return False
@staticmethod
def telefon_validieren(telefon):
"""Prüft, ob eine Telefonnummer gültig ist."""
if not telefon:
return True # Leere Nummer ist ok (optional)
# Erlaubte Zeichen: Ziffern, +, -, Leerzeichen, Klammern
bereinigt = re.sub(r'[\s\-\(\)\+/]', '', telefon)
if bereinigt.isdigit() and len(bereinigt) >= 4:
return True
return False
@staticmethod
def name_validieren(name):
"""Prüft, ob ein Name gültig ist."""
if not name or len(name.strip()) < 1:
return False
return True
@classmethod
def kontakt_validieren(cls, vorname, nachname, telefon="", email=""):
"""Validiert alle Kontaktfelder und gibt Fehlerliste zurück."""
fehler = []
if not cls.name_validieren(vorname):
fehler.append("Vorname darf nicht leer sein.")
if not cls.name_validieren(nachname):
fehler.append("Nachname darf nicht leer sein.")
if not cls.telefon_validieren(telefon):
fehler.append("Ungültige Telefonnummer.")
if not cls.email_validieren(email):
fehler.append("Ungültige E-Mail-Adresse.")
return fehler
Jetzt integrieren wir die Validierung in die Kontakterstellung:
def kontakt_erstellen_validiert():
"""Erstellt einen neuen Kontakt mit Validierung."""
print("\n--- Neuen Kontakt erstellen ---")
while True:
vorname = input("Vorname*: ").strip()
nachname = input("Nachname*: ").strip()
telefon = input("Telefon: ").strip()
email = input("E-Mail: ").strip()
adresse = input("Adresse: ").strip()
notizen = input("Notizen: ").strip()
fehler = Validator.kontakt_validieren(vorname, nachname, telefon, email)
if fehler:
print("\n Fehler bei der Eingabe:")
for f in fehler:
print(f" - {f}")
wiederholen = input("\n Nochmal versuchen? (j/n): ").strip().lower()
if wiederholen != "j":
return None
else:
return Contact(vorname, nachname, telefon, email, adresse, notizen)
Schritt 8: Import/Export als CSV
CSV-Dateien können in Excel oder Google Sheets geöffnet werden — sehr praktisch für den Datenaustausch:
import csv
def als_csv_exportieren(kontakte_liste, dateipfad="kontakte.csv"):
"""Exportiert alle Kontakte als CSV-Datei."""
if not kontakte_liste:
print(" Keine Kontakte zum Exportieren.")
return
felder = ["vorname", "nachname", "telefon", "email", "adresse", "notizen"]
try:
with open(dateipfad, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=felder, delimiter=";")
writer.writeheader()
for k in kontakte_liste:
if isinstance(k, Contact):
writer.writerow(k.to_dict())
else:
writer.writerow(k)
print(f" {len(kontakte_liste)} Kontakte nach '{dateipfad}' exportiert.")
except IOError as e:
print(f" Fehler beim Exportieren: {e}")
def aus_csv_importieren(dateipfad="kontakte.csv"):
"""Importiert Kontakte aus einer CSV-Datei."""
if not os.path.exists(dateipfad):
print(f" Datei '{dateipfad}' nicht gefunden.")
return []
importierte = []
try:
with open(dateipfad, "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f, delimiter=";")
for zeile in reader:
kontakt = Contact.from_dict(zeile)
importierte.append(kontakt)
print(f" {len(importierte)} Kontakte aus '{dateipfad}' importiert.")
return importierte
except (IOError, csv.Error) as e:
print(f" Fehler beim Importieren: {e}")
return []
Warum utf-8-sig? Das Byte Order Mark (BOM) sorgt dafür, dass Excel die Datei korrekt mit Umlauten öffnet. Und das Semikolon als Trennzeichen ist in deutschsprachigen Excel-Versionen Standard.
Vollständiger Code
Hier ist das komplette Programm mit der OOP-Struktur:
import json
import os
import csv
import re
class Contact:
"""Repräsentiert einen einzelnen Kontakt."""
def __init__(self, vorname, nachname, telefon="", email="",
adresse="", notizen=""):
self.vorname = vorname
self.nachname = nachname
self.telefon = telefon
self.email = email
self.adresse = adresse
self.notizen = notizen
@property
def voller_name(self):
return f"{self.vorname} {self.nachname}"
def to_dict(self):
return {
"vorname": self.vorname,
"nachname": self.nachname,
"telefon": self.telefon,
"email": self.email,
"adresse": self.adresse,
"notizen": self.notizen
}
@classmethod
def from_dict(cls, daten):
return cls(**{k: daten.get(k, "") for k in
["vorname", "nachname", "telefon", "email",
"adresse", "notizen"]})
def __str__(self):
return f"{self.voller_name} | {self.telefon} | {self.email}"
class Validator:
"""Validiert Kontakt-Eingaben."""
@staticmethod
def email_validieren(email):
if not email:
return True
return bool(re.match(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email
))
@staticmethod
def telefon_validieren(telefon):
if not telefon:
return True
bereinigt = re.sub(r'[\s\-\(\)\+/]', '', telefon)
return bereinigt.isdigit() and len(bereinigt) >= 4
@staticmethod
def name_validieren(name):
return bool(name and name.strip())
@classmethod
def kontakt_validieren(cls, vorname, nachname, telefon="", email=""):
fehler = []
if not cls.name_validieren(vorname):
fehler.append("Vorname darf nicht leer sein.")
if not cls.name_validieren(nachname):
fehler.append("Nachname darf nicht leer sein.")
if not cls.telefon_validieren(telefon):
fehler.append("Ungültige Telefonnummer.")
if not cls.email_validieren(email):
fehler.append("Ungültige E-Mail-Adresse.")
return fehler
class AddressBook:
"""Verwaltet eine Sammlung von Kontakten."""
def __init__(self, dateipfad="kontakte.json"):
self.kontakte = []
self.dateipfad = dateipfad
def hinzufuegen(self, kontakt):
self.kontakte.append(kontakt)
def bearbeiten(self, index):
k = self._kontakt_holen(index)
if not k:
return
print(f"\n--- Bearbeiten: {k.voller_name} ---")
print(" (Leer lassen = beibehalten)")
for attr, label in [("vorname", "Vorname"), ("nachname", "Nachname"),
("telefon", "Telefon"), ("email", "E-Mail"),
("adresse", "Adresse"), ("notizen", "Notizen")]:
aktuell = getattr(k, attr)
neu = input(f" {label} [{aktuell}]: ").strip()
if neu:
setattr(k, attr, neu)
print(" Kontakt aktualisiert.")
def loeschen(self, index):
k = self._kontakt_holen(index)
if not k:
return
if input(f" '{k.voller_name}' löschen? (j/n): ").lower() == "j":
self.kontakte.pop(index)
print(f" Gelöscht.")
def suchen(self, begriff):
begriff = begriff.lower()
return [(i, k) for i, k in enumerate(self.kontakte)
if begriff in str(k).lower() or
begriff in k.adresse.lower() or
begriff in k.notizen.lower()]
def sortieren(self, nach="nachname"):
self.kontakte.sort(key=lambda k: getattr(k, nach, "").lower())
def speichern(self):
daten = [k.to_dict() for k in self.kontakte]
with open(self.dateipfad, "w", encoding="utf-8") as f:
json.dump(daten, f, indent=2, ensure_ascii=False)
print(f" {len(self.kontakte)} Kontakte gespeichert.")
def laden(self):
if not os.path.exists(self.dateipfad):
return
with open(self.dateipfad, "r", encoding="utf-8") as f:
daten = json.load(f)
self.kontakte = [Contact.from_dict(d) for d in daten]
print(f" {len(self.kontakte)} Kontakte geladen.")
def als_csv_exportieren(self, dateipfad="kontakte.csv"):
felder = ["vorname", "nachname", "telefon", "email",
"adresse", "notizen"]
with open(dateipfad, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=felder, delimiter=";")
writer.writeheader()
for k in self.kontakte:
writer.writerow(k.to_dict())
print(f" {len(self.kontakte)} Kontakte als CSV exportiert.")
def aus_csv_importieren(self, dateipfad="kontakte.csv"):
if not os.path.exists(dateipfad):
print(f" Datei nicht gefunden.")
return
with open(dateipfad, "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f, delimiter=";")
for zeile in reader:
self.kontakte.append(Contact.from_dict(zeile))
print(f" Kontakte importiert. Gesamt: {len(self.kontakte)}")
def alle_anzeigen(self):
if not self.kontakte:
print("\n Keine Kontakte.")
return
print(f"\n{'=' * 60}")
print(f" KONTAKTBUCH ({len(self.kontakte)} Kontakte)")
print(f"{'=' * 60}")
for i, k in enumerate(self.kontakte, 1):
print(f" [{i}] {k}")
print(f"{'=' * 60}")
def _kontakt_holen(self, index):
if 0 <= index < len(self.kontakte):
return self.kontakte[index]
print(" Ungültiger Index!")
return None
# ============================================
# HAUPTPROGRAMM
# ============================================
def main():
buch = AddressBook()
# Automatisch laden, wenn Datei existiert
if os.path.exists(buch.dateipfad):
buch.laden()
while True:
print(f"\n{'=' * 40}")
print(f" KONTAKTBUCH")
print(f"{'=' * 40}")
print(" 1. Neuer Kontakt")
print(" 2. Alle anzeigen")
print(" 3. Kontakt bearbeiten")
print(" 4. Kontakt löschen")
print(" 5. Suchen")
print(" 6. Sortieren")
print(" 7. Speichern")
print(" 8. CSV exportieren")
print(" 9. CSV importieren")
print(" 0. Beenden")
print(f"{'=' * 40}")
wahl = input("Wahl: ").strip()
if wahl == "1":
print("\n--- Neuer Kontakt ---")
vn = input(" Vorname*: ").strip()
nn = input(" Nachname*: ").strip()
tel = input(" Telefon: ").strip()
em = input(" E-Mail: ").strip()
adr = input(" Adresse: ").strip()
not_ = input(" Notizen: ").strip()
fehler = Validator.kontakt_validieren(vn, nn, tel, em)
if fehler:
for f in fehler:
print(f" Fehler: {f}")
else:
buch.hinzufuegen(Contact(vn, nn, tel, em, adr, not_))
print(f" '{vn} {nn}' hinzugefügt!")
elif wahl == "2":
buch.alle_anzeigen()
elif wahl == "3":
buch.alle_anzeigen()
try:
idx = int(input(" Nr. zum Bearbeiten: ")) - 1
buch.bearbeiten(idx)
except ValueError:
print(" Bitte eine Zahl eingeben!")
elif wahl == "4":
buch.alle_anzeigen()
try:
idx = int(input(" Nr. zum Löschen: ")) - 1
buch.loeschen(idx)
except ValueError:
print(" Bitte eine Zahl eingeben!")
elif wahl == "5":
begriff = input(" Suchbegriff: ").strip()
treffer = buch.suchen(begriff)
if treffer:
print(f"\n {len(treffer)} Treffer:")
for idx, k in treffer:
print(f" [{idx+1}] {k}")
else:
print(" Keine Treffer.")
elif wahl == "6":
buch.sortieren()
print(" Nach Nachname sortiert.")
elif wahl == "7":
buch.speichern()
elif wahl == "8":
buch.als_csv_exportieren()
elif wahl == "9":
buch.aus_csv_importieren()
elif wahl == "0":
speichern = input(" Vor dem Beenden speichern? (j/n): ").lower()
if speichern == "j":
buch.speichern()
print("\n Auf Wiedersehen!")
break
if __name__ == "__main__":
main()
Erweiterungsideen
- Geburtstag-Erinnerungen: Ein Datumsfeld hinzufügen und mit
datetimean bevorstehende Geburtstage erinnern - Gruppen/Labels: Kontakte in Gruppen organisieren (Familie, Arbeit, Freunde)
- Duplikat-Erkennung: Warnung, wenn ein ähnlicher Kontakt bereits existiert
- Favoriten: Kontakte als Favorit markieren und schnell darauf zugreifen
- GUI mit tkinter: Eine grafische Oberfläche mit Suchfeld und Tabelle
- SQLite-Datenbank: Statt JSON eine richtige Datenbank verwenden
- vCard-Export: Kontakte im vCard-Format (.vcf) exportieren, das von Smartphones gelesen werden kann
Was du gelernt hast
In diesem Projekt hast du folgende Konzepte praktisch angewendet:
- Klassen und Objekte —
ContactundAddressBookals eigenständige Klassen - Properties und Classmethods —
voller_nameals Property,from_dictals Classmethod - JSON-Dateioperationen — Kontakte persistent speichern und laden
- CSV-Import/Export — Datenaustausch mit Tabellenkalkulationen
- Reguläre Ausdrücke — E-Mail- und Telefonnummer-Validierung mit
re - CRUD-Muster — Das universelle Muster für Datenverwaltung
- Fehlerbehandlung — Try/Except für robuste Dateioperationen
- Benutzermenü — Interaktive Konsolensteuerung mit While-Schleife
Pro-Tipp: Das CRUD-Muster, das du hier gelernt hast, ist eines der wichtigsten Konzepte in der Softwareentwicklung. Fast jede Anwendung — ob Webshop, Social Media oder Datenbank — basiert auf Create, Read, Update und Delete. Wenn du dieses Kontaktbuch-Projekt beherrschst, kannst du das gleiche Muster auf beliebige Daten anwenden: Rezepte, Bücher, Aufgaben, Produkte und vieles mehr!