El módulo de Python re se vuelve 20 veces más lento cuando se repite en más de 100 expresiones regulares diferentes

Mi problema es analizar los archivos de registro y eliminar partes variables en cada línea para agruparlos. Por ejemplo:

s = re.sub(r'(?i)User [_0-9A-z]+ is ', r"User .. is ", s) s = re.sub(r'(?i)Message rejected because : (.*?) \(.+\)', r'Message rejected because : \1 (...)', s) 

Tengo alrededor de más de 120 reglas coincidentes como las anteriores.

No he encontrado problemas de rendimiento al buscar sucesivamente en 100 expresiones regulares diferentes. Pero una gran desaceleración se produce cuando se aplican 101 expresiones regulares.

El mismo comportamiento se produce exactamente al reemplazar mis reglas con

 for a in range(100): s = re.sub(r'(?i)caught here'+str(a)+':.+', r'( ... )', s) 

Se volvió 20 veces más lento cuando se usa el rango (101).

 # range(100) % ./dashlog.py file.bz2 == Took 2.1 seconds. == # range(101) % ./dashlog.py file.bz2 == Took 47.6 seconds. == 

¿Por qué está pasando algo así? ¿Y hay alguna solución conocida?

(Sucede en Python 2.6.6 / 2.7.2 en Linux / Windows).

Python mantiene un caché interno para las expresiones regulares comstackdas. Siempre que use una de las funciones de nivel superior que toma una expresión regular, Python primero comstack esa expresión, y el resultado de esa comstackción se almacena en caché.

¿Adivina cuántos elementos puede almacenar el caché ?

 >>> import re >>> re._MAXCACHE 100 

En el momento en que excede el tamaño del caché, Python 2 borra todas las expresiones en caché y comienza con un caché limpio. Python 3 aumentó el límite a 512, pero aún así hace un claro completo.

La solución es que usted mismo almacene en caché la comstackción:

 compiled_expression = re.compile(r'(?i)User [_0-9A-z]+ is ') compiled_expression.sub(r"User .. is ", s) 

Podría usar functools.partial() para agrupar la llamada sub() junto con la expresión de reemplazo:

 from functools import partial compiled_expression = re.compile(r'(?i)User [_0-9A-z]+ is ') ready_to_use_sub = partial(compiled_expression.sub, r"User .. is ") 

luego, más adelante, use ready_to_use_sub(s) para usar el patrón de expresión regular comstackdo junto con un patrón de reemplazo específico.