tidligere i dag fortalte jeg en arbejdskollega, at re.finditer () i Python er normalt den foretrukne måde at tilkendegive input på, snarere end at håndrulle en lekser fra en iterabel af tegn. Han sagde, at det kræver at læse hele filen i hukommelsen. På stedet, jeg sheepishly sagde, at du sandsynligvis kan læse det linje for linje og ringe re.finditer () gentagne gange, hvis token-regeks ikke krydser linjegrænsen.
det er sandt, men ikke et godt svar. Det viser sig at konstruere en kædet re.finditer () fra en iterabel af strenge er mulig, men ikke helt ligetil.
det første forsøg her demonstrerer hovedideen: siden re.finditer () kampe er ikke-overlappende, efter alle kampe er opbrugt, den næste re.finditer () skal genoptages fra resten af den umatchede streng.
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
linjen med kommentaren ##1
kan være et stridspunkt. Hvis re.finditer () havde ikke givet nogen kamp, implementeringsbeslutningen her er at kassere s helt under forudsætning af, at intet i det nogensinde ville matche, men man kunne argumentere for, at et potentielt match kunne krydse indgangsstrenggrænsen. Hvis dette blev ændret til last += s
, ville det fungere, hvis det potentielle match krydser indgangsgrænsen på bekostning af ubegrænset hukommelsesvækst, hvis et langt tidsrum af input aldrig gav nogen match. Den ubegrænsede vækst er nødvendig, fordi det betyder, at et potentielt match er større end input chunk størrelse, så for at få det fulde match, vi er nødt til dynamisk at øge chunk størrelse, hvilket er hvad vi laver her.
et andet problem er, at den sidste kamp kan være en delvis kamp, der fortsætter ind i den næste streng. Hvis vi giver det naivt, ville den, der ringer, få en brudt delvis kamp. For at illustrere dette, Antag, at input er chunked i 8 tegnstrenge:
def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)
det udskriver:
helloworld
ordet “verden” er brudt i to kampe ved en fejltagelse på grund af strenggrænsen. For at løse dette skal vi bare udsætte den sidste kamp og genstarte den næste re.finditer () fra begyndelsen af den sidste kamp. Den fulde arbejdskode bliver:
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
og den udskriver korrekt:
helloworld
den resulterende finditer_chained() accepterer et fillignende objekt, fordi en fil er en iterabel af linjer, som om re.finditer () anvendes på hele filen. Og det virker uden at læse hele filen i hukommelsen.