Vita di un informatico

Prima di oggi, ho detto a un collega di lavoro che ri.finditer () in Python è di solito il modo preferito per tokenizzare l’input, piuttosto che rotolare a mano un lexer da un iterabile di caratteri. Ha detto che richiede la lettura dell’intero file in memoria. Sul posto, ho timidamente detto che probabilmente si può leggere riga per riga e chiamare re.finditer () ripetutamente se il regexp token non attraversa il confine della linea.
Questo è vero, ma non una grande risposta. Si scopre che la costruzione di un re incatenato.finditer () da un iterabile di stringhe è possibile anche se non abbastanza semplice.
Il primo tentativo qui dimostra l’idea principale: dal momento che ri.finditer () le partite non si sovrappongono, dopo che tutte le partite sono esaurite, il prossimo re.finditer () dovrebbe riprendere dal resto della stringa non corrispondente.

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

La riga con il commento ##1 può essere un punto di contesa. Se ri.finditer () non aveva prodotto alcuna corrispondenza, la decisione di implementazione qui è di scartare s interamente supponendo che nulla in esso corrisponderebbe mai, ma si potrebbe sostenere che una potenziale corrispondenza potrebbe attraversare il confine della stringa di input. Se questo fosse cambiato in last += s, funzionerebbe se la corrispondenza potenziale attraversasse il confine dell’input, a scapito della crescita della memoria illimitata se un lungo intervallo dell’input non ha mai prodotto alcuna corrispondenza. La crescita illimitata è necessaria perché ciò significa che una corrispondenza potenziale è più grande della dimensione del blocco di input, quindi per ottenere la corrispondenza completa, dobbiamo aumentare dinamicamente la dimensione del blocco, che è ciò che stiamo facendo qui.
Un altro problema è che l’ultima corrispondenza potrebbe essere una corrispondenza parziale che continua nella stringa successiva. Se lo cediamo ingenuamente, il chiamante otterrebbe una corrispondenza parziale rotta. Per illustrare questo, supponiamo che l’input sia suddiviso in 8 stringhe di caratteri:

def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)

Stampa:

helloworld

La parola “mondo” è suddivisa in due corrispondenze per errore a causa del limite della stringa. Per risolvere questo problema, abbiamo solo bisogno di rinviare l’ultima partita e riavviare il prossimo re.finditer() dall’inizio dell’ultima partita. Il codice di lavoro completo diventa:

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

E stampa correttamente:

helloworld

Il finditer_chained() risultante accetta un oggetto simile a un file perché un file è un iterabile di linee, come se fosse re.finditer() viene applicato all’intero file. E funziona senza leggere l’intero file in memoria.



+