{}const=>[]async()letfn</>var
EntwicklungPython

Generatoren und Iteratoren in Python: Speicher sparen und Leistung steigern

In diesem Artikel erklären wir, wie Sie Generatoren und Iteratoren in Python verwenden, um effizient mit großen Datenmengen zu arbeiten. Praktische Beispiele für Dateiverarbeitung, APIs und Datenpipelines. Lernen Sie, Code zu schreiben, der sich an jede beliebige Informationsmenge anpasst, ohne den Speicher zu überlasten.

К

Kodik

Autor

6 Min. Lesezeit

Stellen Sie sich vor, Sie müssen eine Protokolldatei mit mehreren Gigabyte verarbeiten. Die einfachste Lösung besteht darin, die gesamte Datei in den Speicher zu laden, in Zeilen aufzuteilen und mit der Verarbeitung zu beginnen. Aber was ist, wenn der Arbeitsspeicher nicht ausreicht? Oder wenn es Dutzende solcher Dateien gibt? Genau für solche Situationen gibt es in Python Generatoren und Iteratoren – leistungsstarke Werkzeuge, mit denen Sie mit großen Datenmengen arbeiten können, ohne sie vollständig in den Speicher zu laden.

Was sind Iteratoren?

Ein Iterator ist ein Objekt, mit dem Sie die Elemente einer Sammlung einzeln durchlaufen können, ohne die gesamte Sammlung auf einmal in den Speicher zu laden. In Python ist jedes Objekt, das die Methode __iter__() und __next__() implementiert, ein Iterator.

Wenn Sie for item in collection schreiben, ruft Python implizit iter(collection) auf, um einen Iterator zu erhalten, und ruft dann wiederholt next() auf, um das nächste Element zu erhalten, bis eine Ausnahme StopIteration ausgelöst wird.

# Beispiel eines einfachen Iterators
class CountDown:
    def __init__(self, start):
        self.current = start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

# Anwendung
counter = CountDown(5)
for num in counter:
    print(num)  # Zeigt an: 5, 4, 3, 2, 1

🔥 100.000+ Schüler sind bereits bei uns

Genug Theorie gelesen?
Zeit zu coden!

Kodik — eine App, in der du durch Praxis programmieren lernst. KI-Mentor, interaktive Lektionen, echte Projekte.

🤖 KI 24/7
🎓 Zertifikate
💰 Kostenlos
🚀 Jetzt starten
Heute beigetreten

Generatoren: Iteratoren auf Steroiden

Generatoren sind eine vereinfachte Möglichkeit, Iteratoren zu erstellen. Anstatt eine Klasse mit den Methoden __iter__() und __next__() zu schreiben, erstellen Sie einfach eine Funktion mit dem Schlüsselwort yield. Der Generator speichert seinen Zustand automatisch zwischen den Aufrufen und setzt die Ausführung dort fort, wo er aufgehört hat.

def count_down(start):
    while start > 0:
        yield start
        start -= 1

# Anwendung
for num in count_down(5):
    print(num)  # Zeigt an: 5, 4, 3, 2, 1

Der Vorteil liegt auf der Hand: Der Code ist kompakter und klarer geworden, die Funktionalität ist gleich geblieben.

Warum spart das Speicherplatz?

Der Hauptvorteil von Generatoren ist die Lazy-Evaluation. Elemente werden nur dann erstellt, wenn sie benötigt werden, und können sofort verarbeitet und vergessen werden. Schauen wir uns ein konkretes Beispiel an:

# Schlecht: Wir laden alles in den Speicher
def read_large_file_bad(file_path):
    with open(file_path, 'r') as file:
        lines = file.readlines()  # Die gesamte Datei ist im Speicher!
    return [line.strip().upper() for line in lines]

# Gut: Wir verarbeiten zeilenweise
def read_large_file_good(file_path):
    with open(file_path, 'r') as file:
        for line in file:  # file ist bereits ein Iterator!
            yield line.strip().upper()

# Anwendung
for processed_line in read_large_file_good('huge_log.txt'):
    # Wir bearbeiten eine Zeile nach der anderen
    process(processed_line)

Im ersten Fall, wenn die Datei 2 GB wiegt, wird Ihr Programm mindestens 2 GB RAM belegen. Im zweiten Fall nur wenige Kilobytes, unabhängig von der Dateigröße.

Praktische Anwendungsbeispiele.

Verarbeitung von Daten aus der API

APIs geben Daten oft seitenweise zurück. Der Generator kann diese Komplexität verbergen:

def fetch_all_users(api_client, page_size=100):
    page = 1
    while True:
        users = api_client.get_users(page=page, size=page_size)
        if not users:
            break
        for user in users:
            yield user
        page += 1

# Anwendung
for user in fetch_all_users(api):
    print(user['name'])  # Wir bearbeiten die Nutzer einzeln

Unendliche Folgen

Mit Generatoren können Sie Endlosschleifen erstellen, ohne dass die Gefahr eines Speicherüberlaufs besteht:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Sie können so viele Elemente nehmen, wie Sie benötigen
from itertools import islice
first_ten = list(islice(fibonacci(), 10))
print(first_ten)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Datenverarbeitungsketten

Generatoren eignen sich hervorragend für die Erstellung von Verarbeitungsketten:

def read_csv(file_path):
    with open(file_path) as f:
        for line in f:
            yield line.strip().split(',')

def filter_by_status(rows, status):
    for row in rows:
        if row[2] == status:  # Status in der dritten Spalte
            yield row

def extract_emails(rows):
    for row in rows:
        yield row[1]  # E-Mail in der zweiten Spalte

# Wir bauen das Förderband zusammen
rows = read_csv('users.csv')
active_users = filter_by_status(rows, 'active')
emails = extract_emails(active_users)

# Erst hier beginnt die eigentliche Verarbeitung
for email in emails:
    send_notification(email)

Das Schöne an diesem Ansatz ist, dass jede Stufe des Förderbands unabhängig und testbar ist und der Speicher nur für ein Element verwendet wird.

Ausdrucksgeneratoren

Neben Generatorfunktionen unterstützt Python Generatorausdrücke - eine kompakte Syntax zum Erstellen von Generatoren:

# List comprehension - erstellt eine Liste im Speicher
squares_list = [x**2 for x in range(1000000)]  # Nimmt viel Speicherplatz ein

# Generatorausdruck - erstellt einen Generator
squares_gen = (x**2 for x in range(1000000))  # Nimmt fast keinen Speicherplatz ein

# Kann in Funktionen verwendet werden
total = sum(x**2 for x in range(1000000))  # Effektiv!

Bitte beachten Sie: Statt eckigen Klammern werden runde Klammern verwendet. Dies ist eine der einfachsten und effektivsten Änderungen, die Sie an Ihrem Code vornehmen können, um Speicherplatz zu sparen.

Wann sollten Generatoren NICHT verwendet werden?

Trotz aller Vorteile sind Generatoren nicht immer geeignet:

Wenn Sie mehrfache Zugriffe auf Daten benötigen: Der Generator kann nur einmal durchlaufen werden. Wenn Sie mehrmals auf Daten zugreifen müssen, ist es besser, eine Liste zu verwenden.

Wenn die Zugriffsgeschwindigkeit wichtig ist: Das Element wird sofort nach Index aus der Liste abgerufen, und der Generator muss von Anfang an sortiert werden.

Wenn Daten in den Speicher gelegt werden: Wenn Sie einen kleinen Datensatz haben, sind die Gemeinkosten für die Erstellung eines Generators möglicherweise nicht gerechtfertigt.

Wenn Sie die Länge wissen müssen: len() funktioniert nicht mit Generatoren, und die Berechnung der Länge erfordert eine vollständige Suche.

Fortgeschrittene Techniken.

Delegieren von Generatoren

Mit Hilfe von yield from kann die Ausführung an einen anderen Generator delegiert werden:

def read_multiple_files(*file_paths):
    for path in file_paths:
        with open(path) as f:
            yield from f  # Zeilenweise Lesung delegieren

# Wir lesen mehrere Dateien als einen Stream
for line in read_multiple_files('log1.txt', 'log2.txt', 'log3.txt'):
    process(line)

Zwei-Wege-Kommunikation mit Generatoren

Generatoren können nicht nur Werte über yield zurückgeben, sondern sie auch über die Methode send() übernehmen:

def running_average():
    total = 0
    count = 0
    average = None
    while True:
        value = yield average
        total += value
        count += 1
        average = total / count

# Anwendung
avg = running_average()
next(avg)  # Wir starten den Generator
print(avg.send(10))  # 10.0
print(avg.send(20))  # 15.0
print(avg.send(30))  # 20.0

Wir messen die Effizienz

Schauen wir uns den tatsächlichen Unterschied in der Speichernutzung an:

import sys

# Liste
numbers_list = [x for x in range(1000000)]
print(f"List: {sys.getsizeof(numbers_list) / 1024 / 1024:.2f} MB")

# Generator
numbers_gen = (x for x in range(1000000))
print(f"Generator: {sys.getsizeof(numbers_gen) / 1024:.2f} KB")

Auf meinem Computer zeigt das Ergebnis einen tausendfachen Unterschied: Die Liste belegt etwa 8 MB und der Generator weniger als 0,1 KB.

Befund

Generatoren und Iteratoren sind nicht nur syntaktischer Zucker, sondern ein grundlegendes Werkzeug für die Arbeit mit großen Datenmengen. Sie ermöglichen es Ihnen, effizienteren Code zu schreiben, der sich an Daten jeder Größe anpasst. Zu verstehen, wann und wie man Generatoren verwendet, unterscheidet einen erfahrenen Entwickler von einem Anfänger.

Fangen Sie einfach an: Ersetzen Sie List Comprehensions durch Generator Expressions, wenn Sie keinen mehrfachen Zugriff auf Daten benötigen. Versuchen Sie dann, eine Generatorfunktion für die Dateiverarbeitung zu schreiben. Und nach und nach werden Sie feststellen, dass Sie über Datenströme nachdenken, nicht über Sammlungen im Speicher.

Anlage Kodik bietet strukturierte Kurse für Einsteiger in die Entwicklung, die Python, JavaScript, HTML, CSS und viele andere Technologien abdecken.

Schließen Sie sich unserem Telegram-Kanal, wo Sie Community-Support, zusätzliche Ressourcen und Antworten auf Fragen von erfahrenen Entwicklern finden.

Lernen Sie Programmieren effektiv und mit Freude!

🎯Hör auf zu zögern

Artikel gefallen?
Zeit zum Üben!

Bei Kodik liest du nicht nur — du schreibst sofort Code. Theorie + Praxis = echte Skills.

Sofortige Praxis
🧠KI erklärt Code
🏆Zertifikat

Keine Registrierung • Keine Karte