tidligere i dag fortalte jeg en arbeidskollega som re.finditer () I Python er vanligvis den foretrukne måten å tokenize input, i stedet for å håndrulle en lexer fra en iterable tegn. Han sa at det krever å lese hele filen i minnet. Pa stedet sa jeg sheepishly at du sikkert kan lese den linje for linje og ring re.finditer () gjentatte ganger hvis token regexp ikke krysser linjegrensen.
det er sant, men ikke et godt svar. Det viser seg at du bygger en kjedet re.finditer() fra en iterable av strenger er mulig, men ikke helt grei.
det første forsøket her demonstrerer hovedideen: siden re.finditer () kamper er ikke-overlappende, etter at alle kamper er oppbrukt, er de neste re.finditer () skal fortsette fra resten av den uovertruffen strengen.
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 poeng av strid. Hvis re.finditer () hadde ikke gitt noen kamp, gjennomføringen avgjørelsen her er å forkaste s helt forutsatt at ingenting i det noen gang ville matche, men man kan hevde at en potensiell kamp kan krysse inngangsstrengen grensen. Hvis dette ble endret til last += s
, ville det fungere hvis den potensielle kampen krysser inngangsgrensen, på bekostning av ubegrenset minnevekst hvis et langt spenn av inngangen aldri ga noen kamp. Den grenseløse vekst er nødvendig fordi det betyr at en potensiell kamp er større enn input blings størrelse, så for å få full kamp, vi trenger å dynamisk øke blings størrelse, som er hva vi gjør her.
Et annet problem er at den siste kampen kan være en delvis kamp som fortsetter inn i neste streng. Hvis vi gir det naivt, vil den som ringer få en ødelagt delvis kamp. For å illustrere dette, anta at inngangen er chunked i 8 tegnstrenger:
def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)
den skriver ut:
helloworld
ordet «verden» er brutt i to kamper ved en feil på grunn av strenggrensen. For å fikse dette, trenger vi bare å utsette den siste kampen og starte neste re.finditer () fra begynnelsen av siste kamp. Den fulle arbeidskoden blir:
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 skriver riktig ut:
helloworld
den resulterende finditer_chained() aksepterer et fillignende objekt fordi en fil er en iterable av linjer, som om re.finditer () brukes på hele filen. Og det fungerer uten å lese hele filen i minnet.