Vie d’un informaticien

Plus tôt aujourd’hui, j’ai dit à un collègue de travail que re.finditer() en Python est généralement le moyen préféré de tokeniser l’entrée, plutôt que de lancer à la main un lexer à partir d’un itérable de caractères. Il a dit que cela nécessite de lire tout le fichier en mémoire. Sur place, j’ai dit timidement que vous pouvez probablement le lire ligne par ligne et appeler re.finditer() à plusieurs reprises si l’expression rationnelle du jeton ne franchit pas les limites de la ligne.
C’est vrai, mais pas une bonne réponse. Il s’avère que la construction d’un re enchaîné.finditer() à partir d’un itérable de chaînes est possible bien que pas tout à fait simple.
La première tentative montre ici l’idée principale: depuis re.les correspondances finditer() ne se chevauchent pas, une fois toutes les correspondances épuisées, la prochaine re.finditer() devrait reprendre à partir du reste de la chaîne inégalée.

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 ligne avec le commentaire ##1 peut être un point de discorde. Si re.finditer() n’avait donné aucune correspondance, la décision d’implémentation ici est de rejeter entièrement s en supposant que rien ne correspondrait jamais, mais on pourrait soutenir qu’une correspondance potentielle pourrait franchir la limite de la chaîne d’entrée. Si cela était modifié en last += s, cela fonctionnerait si la correspondance potentielle franchit la limite d’entrée, au détriment de la croissance de la mémoire illimitée si une longue période de l’entrée ne donnait jamais de correspondance. La croissance illimitée est nécessaire car cela signifie qu’une correspondance potentielle est plus grande que la taille du bloc d’entrée, donc pour obtenir la correspondance complète, nous devons augmenter dynamiquement la taille du bloc, ce que nous faisons ici.
Un autre problème est que la dernière correspondance peut être une correspondance partielle qui se poursuit dans la chaîne suivante. Si nous le cédons naïvement, l’appelant obtiendrait une correspondance partielle brisée. Pour illustrer cela, supposons que l’entrée soit découpée en 8 chaînes de caractères:

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

Il affiche:

helloworld

Le mot « monde » est divisé en deux correspondances par erreur en raison de la limite de chaîne. Pour résoudre ce problème, il nous suffit de reporter le dernier match et de redémarrer le prochain re.finditer() depuis le début de la dernière correspondance. Le code de travail complet devient:

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

Et il imprime correctement:

helloworld

Le finditer_chained() résultant accepte un objet de type fichier car un fichier est un itérable de lignes, comme si re.finditer() est appliqué à l’ensemble du fichier. Et cela fonctionne sans lire tout le fichier en mémoire.



+