Die Python-Standardbibliothek bringt Werkzeuge mit, die viele Aufgaben ohne Zusatzpaket lösen: Dateisystem, Textverarbeitung, Datenformate, Nebenläufigkeit, Netzwerk. Wer sie kennt, schreibt kürzere Programme mit weniger Abhängigkeiten. Dieser Artikel stellt die Module vor, die in der Praxis am häufigsten gebraucht werden.

Pfade und Dateisystem: pathlib statt os.path

from pathlib import Path

p = Path("/srv/daten")
datei = p / "bericht" / "2026-09.txt"        # Zusammenfuegen mit /
print(datei.suffix)         # .txt
print(datei.stem)           # 2026-09
print(datei.parent)         # /srv/daten/bericht

# Lesen und schreiben (kuerzer als open/close)
text = datei.read_text(encoding="utf-8")
datei.write_text("Inhalt\n", encoding="utf-8")
daten = datei.read_bytes()

# Verzeichnisse durchlaufen
for f in Path("/var/log").glob("*.log"):
    print(f.name, f.stat().st_size)

for f in Path("/var/log").rglob("*.gz"):     # rekursiv
    print(f)

# Anlegen, verschieben, loeschen
Path("/tmp/neu").mkdir(parents=True, exist_ok=True)
Path("/tmp/neu/a.txt").touch()
# Umbenennen/Verschieben
Path("/tmp/neu/a.txt").rename("/tmp/neu/b.txt")

# Aufraeumen (Vorsicht!)
# shutil.rmtree(Path("/tmp/neu"))

# Metadaten
st = datei.stat()
print(st.st_size, st.st_mtime)
print(datei.exists(), datei.is_file(), datei.is_dir())

pathlib ist der deutlich angenehmere Weg als die alten os.path-Funktionen: Pfade sind Objekte mit Methoden statt Zeichenketten mit Trennzeichen. Für das Kopieren ganzer Bäume bleibt shutil zuständig.

import shutil
shutil.copy2("/etc/hosts", "/tmp/hosts.bak")   # mit Metadaten
shutil.copytree("/srv/app", "/srv/app.bak")
shutil.move("/tmp/x", "/srv/x")
total, used, free = shutil.disk_usage("/")
print(f"frei: {free/1024**3:.1f} GB von {total/1024**3:.1f} GB")

JSON und CSV

import json, csv

# JSON lesen und schreiben
daten = {"server": [{"name": "web01", "ip": "10.0.0.11"}]}
text = json.dumps(daten, indent=2, ensure_ascii=False)
geladen = json.loads(text)

from pathlib import Path
Path("/tmp/s.json").write_text(text, encoding="utf-8")
inhalt = json.loads(Path("/tmp/s.json").read_text(encoding="utf-8"))

# Wichtige Parameter
json.dumps(daten, indent=2)          # lesbar formatiert
json.dumps(daten, ensure_ascii=False) # Umlaute bleiben Umlaute
json.dumps(daten, sort_keys=True)    # stabile Reihenfolge (Tests!)

# CSV lesen
with open("/tmp/daten.csv", newline="", encoding="utf-8") as f:
    for zeile in csv.DictReader(f):
        print(zeile["name"], zeile["ip"])

# CSV schreiben
with open("/tmp/out.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=["name", "ip"])
    w.writeheader()
    w.writerow({"name": "web01", "ip": "10.0.0.11"})

# Aufgaben mit Zitaten, Semikolons, Tabellen
csv.reader(f, delimiter=";", quotechar='"')
csv.reader(f, delimiter="\t")

Wichtig beim CSV-Schreiben: newline="" ist Pflicht, sonst entstehen unter Windows doppelte Zeilenumbrüche. Und encoding="utf-8" immer angeben – die Standardkodierung ist plattformabhängig.

Kommandozeilenargumente: argparse

import argparse

p = argparse.ArgumentParser(
    prog="backup",
    description="Sichert Verzeichnisse als Archiv.")
p.add_argument("quelle", help="zu sicherndes Verzeichnis")
p.add_argument("-z", "--ziel", default="/srv/backup",
               help="Zielverzeichnis (Standard: %(default)s)")
p.add_argument("-k", "--komprimierung", choices=["none","gzip","zstd"],
               default="gzip")
p.add_argument("-v", "--verbose", action="store_true",
               help="ausfuehrliche Ausgabe")
p.add_argument("--trocken", action="store_true",
               help="nur anzeigen, nichts schreiben")

args = p.parse_args()
if args.verbose:
    print(f"Sichere {args.quelle} nach {args.ziel}")

# Aufruf:  python backup.py /etc -z /srv/backup -k zstd -v
# --help wird automatisch erzeugt

Subprozesse: externes Programm aufrufen

import subprocess

# Empfohlen: Liste von Argumenten, keine Shell
r = subprocess.run(["df", "-h"], capture_output=True, text=True, timeout=10)
print(r.returncode)
print(r.stdout)

# Mit Fehlerpruefung
try:
    subprocess.run(["systemctl", "is-active", "--quiet", "nginx"],
                   check=True, timeout=5)
    print("laeuft")
except subprocess.CalledProcessError:
    print("laeuft nicht")

# Shell nur, wenn wirklich noetig (Pipes, Umleitung)
r = subprocess.run("df -h | tail -1", shell=True, capture_output=True, text=True)

# WICHTIG: Niemals Nutzereingaben mit shell=True verkettet verwenden.
# Das ist der klassische Weg zur Kommando-Einschleusung.
# SCHLECHT: subprocess.run(f"ping {host}", shell=True)
# GUT:      subprocess.run(["ping", "-c", "1", host], timeout=5)

Datum und Zeit

from datetime import datetime, timedelta, timezone

jetzt = datetime.now(timezone.utc)          # immer mit Zeitzone!
print(jetzt.isoformat())                    # 2026-09-26T21:30:00+00:00

# Rechnen
morgen = jetzt + timedelta(days=1)
vorwoche = jetzt - timedelta(weeks=1)

# Formatieren und parsen
print(jetzt.strftime("%Y-%m-%d %H:%M:%S"))
d = datetime.strptime("2026-09-26 14:30", "%Y-%m-%d %H:%M")

# Zeitzonen
from zoneinfo import ZoneInfo
berlin = jetzt.astimezone(ZoneInfo("Europe/Berlin"))

# Fuer Zeitmessungen: perf_counter statt now()
import time
t0 = time.perf_counter()
# ... arbeit ...
print(f"{(time.perf_counter()-t0)*1000:.1f} ms")

Regel: Immer zeitzonenbewusste Zeitstempel verwenden. Ein datetime.now() ohne Zeitzone ist die Quelle unzähliger Fehler bei der Auswertung von Protokollen.

Weitere Module, die Zeit sparen

  • collections: defaultdict (Wörterbuch mit Standardwert), Counter (zählen), deque (schnelle Warteschlange an beiden Enden), namedtuple.
  • itertools: chain, groupby, islice, combinations – mächtige Bausteine ohne eigene Schleifen.
  • re: Reguläre Ausdrücke. Immer mit re.compile() vorbereiten, wenn dieselbe Suche mehrfach läuft, und r"..." für das Muster verwenden.
  • logging: Protokollierung statt print. Mit logging.basicConfig(level=logging.INFO) und Formatangabe lässt sich der Detaillierungsgrad über die Umgebung steuern.
  • dataclasses: @dataclass erzeugt Konstruktor, Vergleich und Darstellung automatisch – ideal für Konfigurations- und Datenklassen.
  • functools: lru_cache für Ergebnisse teurer Funktionen, partial für vorbelegte Argumente.
  • hashlib und secrets: Prüfsummen und Zufallswerte. Für Kennwörter und Token immer secrets statt random.
  • urllib.parse: Adressen zusammensetzen und zerlegen – sicherer als Zeichenkettenverkettung.
  • tempfile: NamedTemporaryFile und TemporaryDirectory räumen sich selbst auf.
# Beispiele fuer die wichtigsten
from collections import defaultdict, Counter
zaehler = Counter(["a","b","a","c","a"])       # Counter({'a': 3, ...})
nach_host = defaultdict(list)
nach_host["web01"].append("nginx")             # kein KeyError

from dataclasses import dataclass, field
@dataclass
class Server:
    name: str
    ip: str
    dienste: list[str] = field(default_factory=list)

s = Server("web01", "10.0.0.11")
print(s)      # Server(name='web01', ip='10.0.0.11', dienste=[])

from functools import lru_cache
@lru_cache(maxsize=128)
def teuer(n):
    return sum(i*i for i in range(n))

import logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s")
logging.info("gestartet")

Wann ein Zusatzpaket sinnvoll ist

Die Standardbibliothek deckt vieles, aber nicht alles ab. Für diese Aufgaben lohnt sich ein bewährtes Zusatzpaket:

  • HTTP-Anfragen: requests oder httpx – deutlich angenehmer als urllib.request.
  • Tabellen und Zahlen: pandas und numpy.
  • Kommandozeile ohne Konfiguration: typer leitet die Schnittstelle aus Typhinweisen ab.
  • Datenprüfung: pydantic für Konfiguration und API-Eingaben.
  • Tests: pytest statt unittest – kürzer und aussagekräftiger.

Verwandte Grundlagen: Python: Funktionen und Module, Python: Dateien lesen und schreiben, python-Befehle, Python: Exceptions.