Weiter zum Inhalt

Eigene Context Manager in Python schreiben

Lerne die fortgeschrittenen Aspekte des Ressourcenmanagements in Python, indem du eigene Context Manager schreibst.
Aktualisiert 18. Sept. 2026

Mit KI erkunden

ChatGPTClaudePerplexity

Motivation

Vielleicht kennst du das with-Statement bereits als kompakten Weg, um Ressourcen in Python zu verwalten. Aber hast du dich schon einmal gefragt, was dahintersteckt? Die Stärke des with-Keywords kommt von Context Managern.

Context Manager sind ein grundlegendes Entwurfsmuster in Python. Sie sorgen dafür, dass Ressourcen angefordert, korrekt genutzt und am Ende wieder freigegeben oder aufgeräumt werden – selbst bei Fehlern oder Ausnahmen.

Das ist besonders wichtig bei Ressourcen wie Dateien, Netzwerkverbindungen oder Datenbank-Handles. Mit Context Managern schreiben wir sauberen, verlässlichen Code und müssen uns nicht mehr darum kümmern, eine Datei zu schließen oder ein Lock zu lösen.

In diesem Tutorial gehen wir über die Standard-Context-Manager von Python hinaus und lernen, unsere eigenen zu schreiben.

Context Manager in Python verstehen

Unter der Haube sind Context Manager Objekte mit zwei speziellen Methoden: __enter__ und __exit__. Die Methode __enter__ wird beim Eintritt in den with-Block aufgerufen, und ihr Rückgabewert wird innerhalb dieses Blocks einer Variable zugewiesen. Die Methode __exit__ wird beim Verlassen des with-Blocks aufgerufen – unabhängig davon, ob der Block normal endet oder durch eine Ausnahme.

Diese Struktur stellt ein sauberes Ressourcenhandling sicher. Schauen wir uns einige eingebaute Context Manager in Python an:

1. Dateiverwaltung

Nehmen wir das klassische Beispiel des Dateiöffnens:

with open('file.txt', 'r') as file:
   data = file.read()

Hier fungiert open('file.txt', 'r') als Context Manager. Beim Eintritt in den with-Block wird die enter-Methode des Dateiobjekts aufgerufen, öffnet die Datei und weist sie der Variablen file zu. Anschließend kannst du mit file.read() auf den Inhalt zugreifen.

Beim Verlassen des with-Blocks wird garantiert die __exit__-Methode des Dateiobjekts aufgerufen – auch bei Ausnahmen. Diese Methode schließt die Datei und verhindert so offene Datei-Handles.

2. Thread-Locking

Über Dateien hinaus lassen sich Context Manager auch zur Threadsynchronisation mit threading.Lock() nutzen:

import threading

lock = threading.Lock()
with lock:
   # Critical section
   print("This code is executed under lock protection.")

Hier ist lock ein threading.Lock-Objekt, ebenfalls ein Context Manager. Beim Eintritt in den with-Block erwirbt __enter__ das Lock, sodass jeweils nur ein Thread den kritischen Abschnitt ausführt. Beim Verlassen gibt __exit__ das Lock wieder frei.

3. Datenbankverbindungen

Context Manager können ebenso Datenbankverbindungen verwalten:

import sqlite3

with sqlite3.connect('database.db') as connection:
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM table")
    rows = cursor.fetchall()

Der Aufruf sqlite3.connect('database.db') liefert einen Context Manager. Beim Eintritt in den with-Block wird die Verbindung aufgebaut und connection zugewiesen. Über einen cursor interagierst du mit der Datenbank. __exit__ stellt sicher, dass die Verbindung beim Verlassen geschlossen wird und keine Ressourcen lecken.

4. Netzwerksockets

Context Manager können sogar Netzwerkkommunikation kapseln:

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(('localhost', 8080))
    s.sendall(b'Hello, world')
    data = s.recv(1024)

Hier erzeugt socket.socket(socket.AF_INET, socket.SOCK_STREAM) ein Socket-Objekt als Context Manager. __enter__ erzeugt das Socket; im with-Block kannst du verbinden, senden und empfangen. __exit__ schließt das Socket sauber.

5. Verzeichnisscans

Diesmal bietet os.scandir('.') eine Möglichkeit, über Verzeichniseinträge zu iterieren:

with os.scandir('.') as entries:
   for entry in entries:
       print(entry.name)

os.scandir('.') wirkt hier als Context Manager. __enter__ startet den Scan, innerhalb des with-Blocks iterierst du über die Einträge. __exit__ räumt den Scan wieder auf.

Wie du siehst, vereinfacht das with-Statement dank Context Managern das Ressourcenmanagement, indem Zuweisung und Freigabe automatisch erfolgen.

Eigene Context Manager in Python schreiben

Schauen wir uns nun an, wie du eigene Manager schreibst, um das Ressourcenmanagement feiner zu steuern.

Es gibt zwei Hauptansätze: klassenbasiert und funktionsbasiert.

Klassenbasierter Ansatz

Der klassenbasierte Ansatz ist die strukturierteste und flexibelste Methode. Du definierst eine Klasse, die die speziellen Methoden __enter__ und __exit__ implementiert.

Hier ein Beispiel einer Timer-Klasse, die die Laufzeit misst:

import time


class Timer:
   def __enter__(self):
       self.start_time = time.time()
       return self

   def __exit__(self, exc_type, exc_value, traceback):
       self.end_time = time.time()
       elapsed_time = self.end_time - self.start_time
       print(f"Elapsed time: {elapsed_time} seconds")


# Example usage
if __name__ == "__main__":
   with Timer() as timer:
       # Code block to measure the execution time
       time.sleep(2)  # Simulate some time-consuming operation
Elapsed time: 2.002082347869873 seconds

Die Timer-Klasse setzt in __enter__ die Startzeit und gibt self zurück, damit du im Block auf das Objekt zugreifen kannst. __exit__ berechnet beim Verlassen die verstrichene Zeit und gibt sie aus.

Funktionsbasierter Ansatz

Wenn du es knapper magst und etwas Flexibilität eintauschen willst, ist der funktionsbasierte Ansatz eine gute Option. Hier nutzt du den contextmanager-Decorator aus dem Modul contextlib, um eine Funktion in einen Context Manager zu verwandeln.

So funktioniert es:

import time
from contextlib import contextmanager


@contextmanager
def timer():
   start_time = time.time()
   yield
   end_time = time.time()
   elapsed_time = end_time - start_time
   print(f"Elapsed time: {elapsed_time} seconds")


# Example usage
if __name__ == "__main__":
   with timer():
       time.sleep(2)
Elapsed time: 2.0020740032196045 seconds

Der Decorator @contextmanager macht aus der Funktion timer einen Context Manager. In der Funktion wird start_time gesetzt, und mit yield pausiert die Ausführung, damit der Code im with-Block läuft.

Die __exit__-Logik folgt nach dem yield: Endezeit messen und die verstrichene Zeit ausgeben.

Im Kern schreibst du die __enter__-Logik vor yield und die __exit__-Logik danach. Beide Ansätze erreichen dasselbe Ziel – wähle nach Vorliebe für Struktur und Lesbarkeit.

Praktische Beispiele für Context Manager

Da du nun weißt, wie man eigene Context Manager schreibt, schauen wir uns praktische Anwendungsfälle an.

Dateioperationen verwalten

Dateioperationen sind in vielen Anwendungen Standard. Ob Lesen oder Schreiben – sauberes Management sichert Datenintegrität und effiziente Ressourcennutzung. Betrachten wir einen Fall, in dem wir Text mit einem eigenen Context Manager in eine Datei schreiben möchten.

class FileManager:
   def __init__(self, filename, mode):
       self.filename = filename
       self.mode = mode
       self.file = None

   def __enter__(self):
       self.file = open(self.filename, self.mode)
       return self.file

   def __exit__(self, exc_type, exc_value, traceback):
       if self.file:
           self.file.close()


# Example usage
if __name__ == "__main__":
   with FileManager("example.txt", "w") as file:
       file.write("Hello, world!\n")

In diesem Beispiel definieren wir eine Klasse FileManager, die die Funktion open nachbildet und Dateioperationen kapselt. Das machen die Methoden im Einzelnen:

  • __init__(self, filename, mode): Konstruktor der Klasse. Initialisiert das FileManager-Objekt mit Dateiname und Modus (z. B. lesen, schreiben, anhängen).
  • __enter__(self): Wird beim Eintritt in den with-Block aufgerufen. Öffnet die Datei im angegebenen Modus, speichert sie in self.file und gibt das Dateiobjekt zurück.
  • __exit__(self, exc_type, exc_value, traceback): Wird beim Verlassen des with-Blocks aufgerufen – auch bei Ausnahmen. Schließt die Datei über self.file.close(), sofern self.file nicht None ist. Das verhindert Leaks und sorgt für sauberes Aufräumen.

Wenn du den Inhalt von example.txt nach der Ausführung prüfst, siehst du den geschriebenen Text:

$ cat example.txt
Hello, world!

Der Vorteil der Klasse FileManager ist, dass du beliebige eigene Logik rund um Dateien einbauen kannst. Zum Beispiel könnte die Klasse protokollieren, welche Dateien geöffnet oder geschlossen wurden.

import datetime


class FileManager:
   def __init__(self, filename, mode, log_filename="file_log.txt"):
       self.filename = filename
       self.mode = mode
       self.log_filename = log_filename
       self.file = None

   def log_action(self, action):
       timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
       with open(self.log_filename, "a") as log_file:
           log_file.write(f"{timestamp} - {action}: {self.filename}\n")

   def __enter__(self):
       self.file = open(self.filename, self.mode)
       self.log_action("Opened")
       return self.file

   def __exit__(self, exc_type, exc_value, traceback):
       if self.file:
           self.file.close()
           self.log_action("Closed")


# Example Usage
with FileManager("example.txt", "r") as file:
   content = file.read()
   print(content)

Hello, world!

In dieser erweiterten Version gilt:

  • Das Modul datetime liefert Zeitstempel.
  • Die Methode log_action protokolliert Dateiaktionen.
  • Der Parameter log_filename legt den Namen der Logdatei fest (Standard: 'file_log.txt').
  • Dateiname und Aktion (Opened/Closed) werden in __enter__ und __exit__ über log_action geloggt.
  • In log_action formatiert datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") den Zeitstempel als "YYYY-MM-DD HH:MM:SS".
  • Der Zeitstempel wird gemeinsam mit Dateiname und Aktion ins Log geschrieben.

Wenn wir jetzt den Inhalt von file_log.txt ausgeben, sehen wir die Einträge:

$ cat file_log.txt
2024-04-03 12:37:02 - Opened: example.txt
2024-04-03 12:37:02 - Closed: example.txt

Ein weiterer praxisnaher Anwendungsfall für einen eigenen Context Manager ist ein kleines SQLite-Verwaltungstool:

import sqlite3


class DatabaseConnection:
   def __init__(self, database_name):
       self.database_name = database_name
       self.connection = None

   def __enter__(self):
       self.connection = sqlite3.connect(self.database_name)
       return self.connection

   def __exit__(self, exc_type, exc_value, traceback):
       if self.connection:
           self.connection.close()

Das entspricht im Kern sqlite3.connect, lässt sich aber um eigene Logik erweitern. Testen wir den Manager mit ein paar Beispiel-Funktionen:

# Example usage
def create_table():
   with DatabaseConnection("example.db") as connection:
       cursor = connection.cursor()
       cursor.execute(
           """CREATE TABLE IF NOT EXISTS users (
                           id INTEGER PRIMARY KEY,
                           username TEXT,
                           email TEXT)"""
       )


def insert_data(username, email):
   with DatabaseConnection("example.db") as connection:
       cursor = connection.cursor()
       cursor.execute(
           "INSERT INTO users (username, email) VALUES (?, ?)", (username, email)
       )
       connection.commit()


def fetch_data():
   with DatabaseConnection("example.db") as connection:
       cursor = connection.cursor()
       cursor.execute("SELECT * FROM users")
       return cursor.fetchall()


if __name__ == "__main__":
   create_table()
   insert_data("john_doe", "john@example.com")
   insert_data("jane_doe", "jane@example.com")
   users = fetch_data()
   print("Users in the database:")
   for user in users:
       print(user)

Users in the database:
(1, 'john_doe', 'john@example.com')
(2, 'jane_doe', 'jane@example.com')
(3, 'john_doe', 'john@example.com')
(4, 'jane_doe', 'jane@example.com')

Indem wir Datenbankoperationen in Funktionen aufteilen und den eigenen Manager nutzen, wird der Code deutlich wartbarer.

Fortgeschrittene Themen rund um Context Manager in Python

In diesem Abschnitt besprechen wir fortgeschrittene Aspekte von Context Managern in Python: Fehlerbehandlung und das Schachteln mehrerer Context Manager.

Fehlerbehandlung

Fehlerbehandlung ist essenziell, und Context Manager bieten eine komfortable Möglichkeit, Fehler im Rahmen des Ressourcenmanagements zu adressieren.

Im Beispiel der Klasse FileManager können wir in __exit__ eine Fehlerbehandlung ergänzen, damit auch bei Ausnahmen sauber aufgeräumt wird. Tritt etwa beim Schreiben in die Datei ein Fehler auf (innerhalb des with-Blocks), soll die Datei dennoch geschlossen werden. So könnte __exit__ aussehen:

def __exit__(self, exc_type, exc_value, traceback):
   if self.file:
       self.file.close()
       if exc_type is not None:
           print(f"An error occurred: {exc_value}")

Zuerst schließen wir die Datei mit self.file.close(). Danach prüfen wir, ob eine Ausnahme auftrat (exc_type is not None). Falls ja, geben wir eine Fehlermeldung aus.

Dabei gilt:

  • exc_type ist die Ausnahmeklasse, z. B. AttributeError oder IndexError.
  • exc_value ist die Instanz mit der Fehlermeldung.
  • traceback ist das vollständige Traceback-Objekt.

Führen wir die Klasse FileManager mit absichtlich fehlerhaftem Code aus:

with FileManager("example.txt", "r") as file:
   # Simulate a sample error
   content = file.read()
   print(content[150])

An error occurred: string index out of range

Wenn du in eigenen Managern Ausnahmen individuell behandeln willst, kannst du dich an folgendem Ablauf orientieren:

def __exit__(self, exc_type, exc_value, traceback):
   # Perform cleanup
   ...

   # Handle the exception
   if isinstance(exc_value, AttributeError):
       # Handle AttributeError here
       print(f"Exception message: {exc_value}")
   # Handle another exception
   elif isinstance(exc_value, AnotherError):
       ...

Context Manager schachteln

Beim Schachteln von Context Managern verwaltest du mehrere Ressourcen gleichzeitig innerhalb eines einzigen with-Statements – jede mit ihrem eigenen Manager. Das ist hilfreich bei komplexen Vorgängen mit mehreren Ressourcen. So funktioniert es in unseren Beispielen:

Angenommen, wir möchten eine Datei öffnen und zugleich Datenbankoperationen ausführen, etwa eine CSV-Datei zeilenweise in eine Datenbank schreiben. Wir können FileManager und DatabaseConnection in einem einzigen with-Statement kombinieren:

if __name__ == "__main__":
   with FileManager("example.txt", "w") as file, DatabaseConnection("example.db") as connection:
       ...

In diesem Beispiel sind FileManager und DatabaseConnection in demselben with-Statement verschachtelt. So öffnen wir die Datei und bauen die Datenbankverbindung gleichzeitig auf. Anschließend erfolgen Datei- und Datenbankoperationen im gemeinsamen Kontext.

Behalte die Lesbarkeit im Blick. Empfehlenswert ist, nicht mehr als zwei Kontexte gleichzeitig zu kombinieren.

Fazit

Context Manager in Python bieten innerhalb eines with-Statements eine strukturierte Herangehensweise an das Ressourcenmanagement und sorgen für korrekte Zuweisung und Freigabe.

In diesem Tutorial haben wir die Grundlagen behandelt, praktische Beispiele durchgespielt und fortgeschrittene Themen wie Fehlerbehandlung und Verschachtelung besprochen. Wenn du eigene Context Manager beherrschst, schreibst du saubereren, verlässlicheren Code, verbesserst die Fehlerbehandlung und nutzt Ressourcen effizient.

Das Schreiben eigener Context Manager gehört zur Kategorie „Advanced Python“. Hier findest du passende Ressourcen, um deine Kompetenzen weiter auszubauen:


Bexruz (Bex) Tuychiev's photo
Author
Bexruz (Bex) Tuychiev
LinkedIn

Ich bin Content-Creator im Bereich Data Science mit über zwei Jahren Erfahrung und zähle zu den größten Stimmen auf Medium. Ich schreibe gern ausführliche Artikel über KI und ML – mit einer Prise Sarkasmus, damit das Ganze nicht zu trocken wird. Bisher habe ich über 130 Artikel veröffentlicht und einen DataCamp-Kurs produziert, ein weiterer ist in Arbeit. Meine Inhalte wurden von über 5 Millionen Menschen gelesen, 20.000 davon folgen mir auf Medium und LinkedIn. 

Themen
Python

Setze deine Python-Reise heute fort!

Lernpfad

Python-Programmierung

19 Std.
Verbessere deine Programmierkenntnisse. Lerne, wie du Code optimierst, Funktionen und Tests schreibst und Best-Practice-Techniken der Softwareentwicklung anwendest.
Details anzeigenRight Arrow
Kurs Starten
Mehr anzeigenRight Arrow
Verwandt

Tutorial

Python-Cache: Zwei einfache Methoden

Lerne, wie du Dekoratoren wie @functools.lru_cache oder @functools.cache benutzt, um Funktionen in Python zwischenzuspeichern.
Stephen Gruppetta's photo

Stephen Gruppetta

12 Min.

Tutorial

30 coole Python-Tricks für besseren Code mit Beispielen

Wir haben 30 coole Python-Tricks zusammengestellt, mit denen du deinen Code verbesserst und deine Python-Kompetenzen ausbaust.
Kurtis Pykes 's photo

Kurtis Pykes

15 Min.

Tutorial

Abstrakte Klassen in Python: Ein umfassender Leitfaden mit Beispielen

Lerne mehr über abstrakte Klassen in Python, wozu sie gut sind und wie du mit dem Modul „abc“ einheitliche Schnittstellen sicherstellen kannst. Enthält praktische Beispiele und bewährte Methoden für eine effektive Umsetzung.
Derrick Mwiti's photo

Derrick Mwiti

10 Min.

Tutorial

So kürzt man eine Zeichenfolge in Python: Drei verschiedene Methoden

Lerne die Grundlagen zum Entfernen von führenden und nachfolgenden Zeichen aus einer Zeichenfolge in Python.
Adel Nehme's photo

Adel Nehme

6 Min.

Tutorial

Python Hallo Welt: Ein Leitfaden für Programmieranfänger

Lerne die Grundlagen von Python, indem du das Programm print(„Hello World“) ausführst.
Adel Nehme's photo

Adel Nehme

3 Min.

Tutorial

Python Switch Case Statement: Ein Leitfaden für Anfänger

Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Matt Crabtree's photo

Matt Crabtree

5 Min.

Mehr AnzeigenMehr Anzeigen