Iteratoren als Filter

Für meine Plotroutinen brauche ich oft Funktionen, die eine Liste an Messwerten durchgehen und nur diejenigen zurückgeben, die einem bestimmten Kriterium entsprechen. Wenn dabei die Anzahl der Messwerte in die Millionen geht, kann alleine schon die Liste der ungefilterten Messwerte den Arbeitsspeicher des Rechners sprengen.

Ich könnte jetzt einfach eine Funktion schreiben, die alle Werte liest, filtert und nur die Relevanten zurückgibt.

Das könnte dann etwa so aussehen:

def naivefilter(filepath, name):
    """Get only the relevant values."""
    values = []
    with open(filepath) as f:
        for line in f:
            if line.startswith(name):
                values.append(line)
    return values

Wirkt im Kleinen noch übersichtlich. Aber sobald wir die Anzahl der Filterkriterien erhöhen - oder andere Eingabewerte brauchen - wird die Funktion unübersichtlich. Und unübersichtlicher Code ist unschön zu warten.

Um das Problem zu minimieren, nutze ich Iteratoren, die nur jeweils die benötigten Werte im Speicher zu behalten, aber von Funktion zu Funktion weitergegeben werden können. Damit kann ich die Logik zur Auswahl der Werte aus der auslesenden Funktion heraushalten und erhalte damit zwei schlanke Funktionen.

Das ganze sieht dann beispielsweise so aus:

def allvalues(filepath):
    """Get all values from the file."""
    with open(filepath) as f:
        for line in f:
            yield line
def valuesforstation(filepath, name): """Get all the values from a file which come from the given station.""" return tuple(line for line in allvalues(filepath) if line.startswith(name))

Wirkt anfangs etwas länger, aber was passiert, wenn wir ein anderes Filterkriterium haben wollen?

Dann fügen wir einfach eine andere Filterfunktion hinzu:

def valueswithoutX(filepath, X):
    """Get all the values from a file 
    which do not contain the value X."""
    return tuple(line for line in allvalues(filepath) 
                 if not X in line)

Damit haben wir das Auslesen und das Filtern der Daten sauber getrennt, ohne jemals alle Werte gleichzeitig im Speicher halten zu müssen.

Der Code ist wartbar, übersichtlich und effizient. Und damit ist er genau so, wie ich ihn haben will.

Und falls ihr euch fragt, warum ich am Ende einen tuple zurückgebe und keinen weiteren Iterator: Ich habe noch einen Caching-Dekorator, und der mag keine Iteratoren (weil die theoretisch unendlich viele Werte zurückgeben können und damit nicht immer gecacht werden können). Aber dazu kommen wir ein ander’ Mal…1 (bis dahin kann ich euch den Leitfaden zu Dekoratoren von Simon Franklin empfehlen, wenn ihr mit fortgeschritteneren Techniken experimentieren wollt :) ).

PS: Ja, ich könnte auch eine Datenbank nutzen. Dann muss ich die allerdings füllen und aktuell halten und wäre so weiter weg von den Daten, auf denen ich eigentlich arbeite. Und die Gesamt-Aufgaben sind schon komplex genug, ohne einen weiteren Indirektionsschritt einzuführen…

PPS: Alle Codeschnipsel in Python, farbige Quelltext-Hervorhebung via M-x htmlize-region in emacs mit inline-css (einstellbar über M-x customize-variable htmlize-output-type)


  1. Soll heißen, dazu kommen wir vielleicht ein ander’ Mal, je nach meinem Zeitbudget… 

Inhalt abgleichen
Willkommen im Weltenwald!
((λ()'Dr.ArneBab))



Beliebte Inhalte

sn.1w6.org news