Traté de reemplazar un carácter a por b en una cadena grande dada. Hice un experimento: primero lo reemplacé en toda la cadena, luego lo reemplacé solo al principio.
import re # pattern = re.compile('a') pattern = re.compile('^a') string = 'x' * 100000 pattern.sub('b', string)Esperaba que reemplazar el principio tendría que ser mucho más rápido que reemplazar toda la cadena porque solo tiene que verificar 1 posición en lugar de 100000. Hice algunas mediciones:
python -m timeit --setup "import re; p=re.compile('a'); string='x'*100000" "p.sub('b', string)" 10000 loops, best of 3: 19.1 usec per loop python -m timeit --setup "import re; p=re.compile('^a'); string='x'*100000" "p.sub('b', string)" 1000 loops, best of 3: 613 usec per loopLos resultados muestran que, por el contrario, tratar de reemplazar toda la cadena es unas 30 veces más rápido. ¿Esperarías tal resultado? ¿Puedes explicar eso?
Las funciones proporcionadas en el módulo Python re no se optimizan en función de los anclajes. En particular, las funciones que intentan aplicar una expresión regular en cada posición ( .search , .sub , .findall , etc.) lo harán incluso cuando la expresión regular solo pueda coincidir al principio. Es decir, incluso sin especificar el modo multilínea, de modo que ^ solo puede coincidir al principio de la cadena, la llamada no se redirige internamente. Por lo tanto:
$ # .match only looks at the first position regardless $ python -m timeit --setup "import re; p=re.compile('a'); string='x'*100000" "p.match(string)" 2000000 loops, best of 5: 155 nsec per loop $ python -m timeit --setup "import re; p=re.compile('^a'); string='x'*100000" "p.match(string)" 2000000 loops, best of 5: 157 nsec per loop $ # .search looks at every position, even if there is an anchor $ python -m timeit --setup "import re; p=re.compile('a'); string='x'*100000" "p.search(string)" 10000 loops, best of 5: 22.4 usec per loop $ # and the anchor only adds complexity to the matching process $ python -m timeit --setup "import re; p=re.compile('^a'); string='x'*100000" "p.search(string)" 500 loops, best of 5: 746 usec per loop Si bien re no optimiza para los anclajes, sí lo hace para varias otras cosas que podrían ocurrir al comienzo de un patrón. Una de esas optimizaciones es para un patrón que comienza con un solo carácter constante :
if (prefix_len == 1) { /* pattern starts with a literal character */ SRE_CHAR c = (SRE_CHAR) prefix[0]; #if SIZEOF_SRE_CHAR < 4 if ((SRE_CODE) c != prefix[0]) return 0; /* literal can't match: doesn't fit in char width */ #endif end = (SRE_CHAR *)state->end; state->must_advance = 0; while (ptr < end) { while (*ptr != c) { if (++ptr >= end) return 0; } ... Esta optimización realiza una comparación de caracteres simple para omitir las coincidencias de candidatos que no comienzan con el carácter requerido, en lugar de invocar el motor de coincidencia completo. Esta optimización es la razón por la cual la expresión regular no anclada fue mucho más rápida: hay 3 optimizaciones separadas como esta en el código, una para un solo carácter constante, otra para un prefijo constante de varios caracteres y otra para una clase de carácter, pero nada para un ^ ancla.
Creo que se puede hacer un caso razonable para presentar un informe de error en contra de esto: no tener una optimización tan obvia implementada claramente viola las expectativas. Aparte de eso, si bien es fácil reemplazar .search con un ancla usando .match , no es tan sencillo reemplazar .sub con un ancla: debe .match , verificar el resultado y luego llamar a .replace en la cadena usted mismo.
Si necesita anclar al final de la cuerda y no al principio, se vuelve mucho más difícil; Recuerdo el antiguo consejo de Perl de intentar invertir la cadena primero, pero en general es difícil escribir un patrón que coincida con el reverso de lo que quieres.