Heute früh habe ich einem Arbeitskollegen gesagt, dass re.finditer() in Python ist normalerweise die bevorzugte Methode zum Tokenisieren von Eingaben, anstatt einen Lexer aus einer iterierbaren Anzahl von Zeichen von Hand zu rollen. Er sagte, dass es das Lesen der gesamten Datei im Speicher erfordert. Vor Ort sagte ich verlegen, dass Sie es wahrscheinlich Zeile für Zeile lesen und re anrufen können.finditer() wiederholt, wenn der Token-Regexp die Liniengrenze nicht überschreitet.
Das stimmt, aber keine gute Antwort. Es stellt sich heraus, dass die Konstruktion eines verketteten re.finditer() aus einer iterable von Strings ist möglich, wenn auch nicht ganz einfach.
Der erste Versuch hier zeigt die Grundidee: seit re.finditer() Übereinstimmungen sind nicht überlappend, nachdem alle Übereinstimmungen erschöpft sind, die nächste re.finditer() sollte vom Rest der nicht übereinstimmenden Zeichenfolge fortgesetzt werden.
def finditer_chained_bad(pattern, strings, flags=0): last = '' for s in strings: m = None for m in re.finditer(pattern, last + s, flags): yield m if not m: last = '' ##1 continue # m is the last match object. last = s
Die Zeile mit dem Kommentar ##1
kann ein Streitpunkt sein. Wenn re.die Implementierungsentscheidung hier besteht darin, s vollständig zu verwerfen, vorausgesetzt, dass nichts darin jemals übereinstimmen würde, aber man könnte argumentieren, dass eine mögliche Übereinstimmung die Grenze der Eingabezeichenfolge überschreiten könnte. Wenn dies in last += s
geändert würde, würde es funktionieren, wenn die potenzielle Übereinstimmung die Eingabegrenze überschreitet, auf Kosten des unbegrenzten Speicherwachstums, wenn eine lange Spanne der Eingabe niemals eine Übereinstimmung ergab. Das unbegrenzte Wachstum ist notwendig, da dies bedeutet, dass eine potenzielle Übereinstimmung größer ist als die Größe des Eingabeblocks.
Ein weiteres Problem besteht darin, dass die letzte Übereinstimmung eine teilweise Übereinstimmung sein kann, die in der nächsten Zeichenfolge fortgesetzt wird. Wenn wir es naiv geben, würde der Anrufer eine kaputte Teilübereinstimmung erhalten. Um dies zu veranschaulichen, nehmen wir an, die Eingabe ist in 8 Zeichenfolgen unterteilt:
def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)
Es wird gedruckt:
helloworld
Das Wort „Welt“ wird aufgrund der Zeichenfolgengrenze versehentlich in zwei Übereinstimmungen aufgeteilt. Um dies zu beheben, müssen wir nur das letzte Spiel verschieben und das nächste Spiel neu starten.finditer() vom Anfang des letzten Spiels. Der vollständige Arbeitscode wird zu:
def finditer_chained(pattern, strings, flags=0): last_s = '' for s in strings: last_m = None last_s += s for m in re.finditer(pattern, last_s, flags): if last_m: yield last_m last_m = m if not last_m: continue assert last_m is m last_s = s if last_m: yield last_m
Und es wird korrekt gedruckt:
helloworld
Das resultierende finditer_chained() akzeptiert ein dateiähnliches Objekt, da eine Datei eine iterierbare von Zeilen ist, als ob re .finditer() wird auf die gesamte Datei angewendet. Und es funktioniert, ohne die gesamte Datei im Speicher zu lesen.