wcześniej dzisiaj powiedziałem koledze z pracy, że re.finditer () w Pythonie jest zwykle preferowanym sposobem tokenizacji danych wejściowych, zamiast ręcznego przetaczania lexera z iteracyjnych znaków. Powiedział, że wymaga odczytu całego pliku w pamięci. Na miejscu, nieśmiało powiedziałem, że prawdopodobnie można przeczytać to linia po linii i zadzwonić do re.finditer () wielokrotnie, jeśli wyrażenie regularne tokena nie przekracza granicy linii.
to prawda, ale niezbyt dobra odpowiedź. Okazuje się, że konstruowanie przykutego re.finditer() z iteratywnego ciągu znaków jest możliwe, choć nie całkiem proste.
pierwsza próba tutaj pokazuje główną ideę: od re.finditer () dopasowania nie nakładają się na siebie, po wyczerpaniu wszystkich dopasowań następny re.finditer() powinna wznowić pracę od pozostałej części niezrównanego ciągu.
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
wiersz z komentarzem ##1
może być punktem spornym. Jeśli re.finditer() nie dała żadnego dopasowania, decyzją implementacyjną jest odrzucenie S całkowicie zakładając, że nic w niej nie pasuje, ale można argumentować, że potencjalny dopasowanie może przekroczyć granicę wejściowego ciągu znaków. Gdyby to zostało zmienione na last += s
, to działałoby, gdyby potencjalny dopasowanie przekroczyło granicę wejściową, kosztem nieograniczonego wzrostu pamięci, jeśli długi zakres danych wejściowych nigdy nie dawał dopasowania. Nieograniczony wzrost jest konieczny, ponieważ oznacza to, że potencjalny mecz jest większy niż wejściowy rozmiar kawałka, więc aby uzyskać pełne dopasowanie, musimy dynamicznie zwiększyć rozmiar kawałka, co robimy tutaj.
innym problemem jest to, że ostatnie dopasowanie może być częściowym dopasowaniem, które kontynuuje się do następnego ciągu. Jeśli poddamy się naiwnie, rozmówca będzie miał zepsute dopasowanie częściowe. Aby to zilustrować, Załóżmy, że wejście jest podzielone na 8 ciągów znaków:
def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)
wypisuje:
helloworld
słowo „świat” jest podzielone na dwa mecze przez pomyłkę ze względu na granicę łańcucha. Aby to naprawić, musimy tylko odroczyć ostatni mecz i ponownie uruchomić następny re.finditer() od początku ostatniego meczu. Pełny kod roboczy staje się:
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
i poprawnie wypisuje:
helloworld
wynikowa finditer_chained() akceptuje obiekt podobny do pliku, ponieważ plik jest iterowalną linią, tak jakby re.metoda finditer() jest stosowana do całego pliku. I działa bez odczytu całego pliku w pamięci.