dříve dnes jsem řekl kolegovi z práce, že re.finditer() v Pythonu je obvykle preferovaný způsob tokenize vstup, spíše než ruční válcování lexer z iterable znaků. Řekl, že to vyžaduje čtení celého souboru v paměti. Na místě, jsem rozpačitě řekl, že si můžete pravděpodobně přečíst řádek po řádku a zavolat re.finditer () opakovaně, pokud token regexp nepřekročí hranici čáry.
to je pravda, ale není to skvělá odpověď. Ukazuje se, že konstrukce zřetězené re.finditer () z iterable řetězců je možné, i když ne zcela jednoduché.
první pokus zde demonstruje hlavní myšlenku: od re.finditer () zápasy se nepřekrývají, po vyčerpání všech zápasů, další re.finditer () by měl pokračovat ze zbytku nesrovnatelného řetězce.
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
řádek s komentářem ##1
může být sporným bodem. Pokud re.finditer() nepřineslo žádné utkání, provádění rozhodnutí je zde zlikvidujte s výhradně za předpokladu, že nic, že by někdy zápas, ale jeden mohl argumentovat, že potenciální zápas mohl být přes vstupní řetězec hranice. Kdyby to byl změněn na last += s
, pak to bude fungovat, pokud potenciální zápas kříže vstupní hranice, na úkor nespoutaný růst paměti, pokud dlouhá rozpětí vstupního nikdy nevydalo žádné utkání. Neomezený růst je nutný, protože to znamená, že potenciální shoda je větší než velikost vstupního bloku, takže abychom získali plnou shodu, musíme dynamicky zvětšit velikost bloku, což je to, co tady děláme.
dalším problémem je, že poslední zápas může být částečná shoda, která pokračuje do dalšího řetězce. Pokud bychom to podali naivně, volající by dostal zlomenou částečnou shodu. Pro ilustraci předpokládejme, že vstup je rozdělen do 8 řetězců znaků:
def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)
vytiskne:
helloworld
slovo „svět“ je omylem rozděleno na dvě shody kvůli hranici řetězce. Chcete-li to opravit, stačí odložit poslední zápas a restartovat další re.finditer () od začátku posledního zápasu. Plný pracovní kód se stává:
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
A je to správně tiskne:
helloworld
výsledná finditer_chained() přijímá soubor-jako objekt, protože soubor je iterable linek, jako kdyby znovu.finditer() je aplikován na celý soubor. A funguje to bez čtení celého souboru v paměti.