¿Vale la pena usar re.compile de Python?

¿Hay algún beneficio en el uso de comstackr para expresiones regulares en Python?

h = re.compile('hello') h.match('hello world') 

vs

 re.match('hello', 'hello world') 

He tenido mucha experiencia ejecutando una expresión regular comstackda miles de veces en lugar de comstackr sobre la marcha, y no he notado ninguna diferencia perceptible. Obviamente, esto es anecdótico, y ciertamente no es un gran argumento en contra de la comstackción, pero he encontrado que la diferencia es insignificante.

EDITAR: Después de echar un vistazo rápido al código real de la biblioteca de Python 2.5, veo que Python comstack internamente AND CACHES regexes cada vez que los usa (incluidas las llamadas a re.match() ), por lo que solo está cambiando CUANDO el regex obtiene comstackdo, y no debería ahorrar mucho tiempo en absoluto, solo el tiempo que lleva verificar la caché (una búsqueda clave en un tipo de dict interno).

Desde el módulo re.py (los comentarios son míos):

 def match(pattern, string, flags=0): return _compile(pattern, flags).match(string) def _compile(*key): # Does cache check at top of function cachekey = (type(key[0]),) + key p = _cache.get(cachekey) if p is not None: return p # ... # Does actual comstacktion on cache miss # ... # Caches compiled regex if len(_cache) >= _MAXCACHE: _cache.clear() _cache[cachekey] = p return p 

Sigo comstackndo con frecuencia expresiones regulares, pero solo para vincularlas a un nombre agradable y reutilizable, no para ninguna ganancia de rendimiento esperada.

Para mí, el mayor beneficio de re.compile es poder separar la definición de la expresión regular de su uso.

Incluso una expresión simple como 0|[1-9][0-9]* (entero en la base 10 sin ceros iniciales) puede ser lo suficientemente compleja como para que no tenga que volver a escribirla, verifique si realizó algún error tipográfico, y luego tendrá que volver a verificar si hay errores tipográficos al iniciar la depuración. Además, es mejor usar un nombre de variable como num o num_b10 que 0|[1-9][0-9]* .

Ciertamente es posible almacenar cadenas y pasarlas a re.match; Sin embargo, eso es menos legible:

 num = "..." # then, much later: m = re.match(num, input) 

Versus comstackndo:

 num = re.compile("...") # then, much later: m = num.match(input) 

Aunque está bastante cerca, la última línea de la segunda se siente más natural y más simple cuando se usa repetidamente.

FWIW:

 $ python -m timeit -s "import re" "re.match('hello', 'hello world')" 100000 loops, best of 3: 3.82 usec per loop $ python -m timeit -s "import re; h=re.compile('hello')" "h.match('hello world')" 1000000 loops, best of 3: 1.26 usec per loop 

Por lo tanto, si va a utilizar mucho la misma expresión regular, puede valer la pena hacer re.compile (especialmente para expresiones regulares más complejas).

Se aplican los argumentos estándar contra la optimización prematura, pero no creo que realmente pierda mucha claridad / re.compile usar re.compile si sospecha que sus expresiones regulares pueden convertirse en un cuello de botella en el rendimiento.

Actualizar:

Bajo Python 3.6 (sospecho que los tiempos anteriores se realizaron con Python 2.x) y el hardware 2018 (MacBook Pro), ahora obtengo los siguientes tiempos:

 % python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 0.661 usec per loop % python -m timeit -s "import re; h=re.compile('hello')" "h.match('hello world')" 1000000 loops, best of 3: 0.285 usec per loop % python -m timeit -s "import re" "h=re.compile('hello'); h.match('hello world')" 1000000 loops, best of 3: 0.65 usec per loop % python --version Python 3.6.5 :: Anaconda, Inc. 

También agregué un caso (note las diferencias de comillas entre las dos últimas ejecuciones) que muestra que re.match(x, ...) es literalmente [aproximadamente] equivalente a re.compile(x).match(...) , es decir, no parece ocurrir ningún almacenamiento en caché entre bastidores de la representación comstackda.

Aquí hay un caso de prueba simple:

 ~$ for x in 1 10 100 1000 10000 100000 1000000; do python -m timeit -n $x -s 'import re' 're.match("[0-9]{3}-[0-9]{3}-[0-9]{4}", "123-123-1234")'; done 1 loops, best of 3: 3.1 usec per loop 10 loops, best of 3: 2.41 usec per loop 100 loops, best of 3: 2.24 usec per loop 1000 loops, best of 3: 2.21 usec per loop 10000 loops, best of 3: 2.23 usec per loop 100000 loops, best of 3: 2.24 usec per loop 1000000 loops, best of 3: 2.31 usec per loop 

con re.compile:

 ~$ for x in 1 10 100 1000 10000 100000 1000000; do python -m timeit -n $x -s 'import re' 'r = re.compile("[0-9]{3}-[0-9]{3}-[0-9]{4}")' 'r.match("123-123-1234")'; done 1 loops, best of 3: 1.91 usec per loop 10 loops, best of 3: 0.691 usec per loop 100 loops, best of 3: 0.701 usec per loop 1000 loops, best of 3: 0.684 usec per loop 10000 loops, best of 3: 0.682 usec per loop 100000 loops, best of 3: 0.694 usec per loop 1000000 loops, best of 3: 0.702 usec per loop 

Por lo tanto, parece que la comstackción es más rápida con este caso simple, incluso si solo haces coincidencias una vez .

Acabo de intentar esto yo mismo. Para el caso simple de analizar un número de una cadena y sumrlo, usar un objeto de expresión regular comstackdo es casi el doble de rápido que usar los métodos re .

Como han señalado otros, los métodos re (incluido re.compile ) buscan la cadena de expresión regular en un caché de expresiones comstackdas previamente. Por lo tanto, en el caso normal, el costo adicional de usar los métodos re es simplemente el costo de la búsqueda de caché.

Sin embargo, el examen del código muestra que el caché está limitado a 100 expresiones. Esto plantea la pregunta, ¿qué tan doloroso es desbordar el caché? El código contiene una interfaz interna para el comstackdor de expresiones regulares, re.sre_compile.compile . Si lo llamamos, saltamos el caché. Resulta que son aproximadamente dos órdenes de magnitud más lentos para una expresión regular básica, como r'\w+\s+([0-9_]+)\s+\w*' .

Aquí está mi prueba:

 #!/usr/bin/env python import re import time def timed(func): def wrapper(*args): t = time.time() result = func(*args) t = time.time() - t print '%s took %.3f seconds.' % (func.func_name, t) return result return wrapper regularExpression = r'\w+\s+([0-9_]+)\s+\w*' testString = "average 2 never" @timed def noncompiled(): a = 0 for x in xrange(1000000): m = re.match(regularExpression, testString) a += int(m.group(1)) return a @timed def compiled(): a = 0 rgx = re.compile(regularExpression) for x in xrange(1000000): m = rgx.match(testString) a += int(m.group(1)) return a @timed def reallyCompiled(): a = 0 rgx = re.sre_compile.compile(regularExpression) for x in xrange(1000000): m = rgx.match(testString) a += int(m.group(1)) return a @timed def compiledInLoop(): a = 0 for x in xrange(1000000): rgx = re.compile(regularExpression) m = rgx.match(testString) a += int(m.group(1)) return a @timed def reallyCompiledInLoop(): a = 0 for x in xrange(10000): rgx = re.sre_compile.compile(regularExpression) m = rgx.match(testString) a += int(m.group(1)) return a r1 = noncompiled() r2 = compiled() r3 = reallyCompiled() r4 = compiledInLoop() r5 = reallyCompiledInLoop() print "r1 = ", r1 print "r2 = ", r2 print "r3 = ", r3 print "r4 = ", r4 print "r5 = ", r5 

And here is the output on my machine:

 $ regexTest.py noncompiled took 4.555 seconds. compiled took 2.323 seconds. reallyCompiled took 2.325 seconds. compiledInLoop took 4.620 seconds. reallyCompiledInLoop took 4.074 seconds. r1 = 2000000 r2 = 2000000 r3 = 2000000 r4 = 2000000 r5 = 20000 

Los métodos 'realmente comstackdos' usan la interfaz interna, que omite la memoria caché. Tenga en cuenta que el que se comstack en cada iteración de bucle solo se itera 10.000 veces, no un millón.

Estoy de acuerdo con Honest Abe en que la match(...) en los ejemplos dados es diferente. No son comparaciones de uno a uno y, por lo tanto, los resultados varían. Para simplificar mi respuesta, uso A, B, C, D para esas funciones en cuestión. Oh sí, estamos tratando con 4 funciones en re.py en lugar de 3.

Ejecutando esta pieza de código:

 h = re.compile('hello') # (A) h.match('hello world') # (B) 

es lo mismo que ejecutar este código:

 re.match('hello', 'hello world') # (C) 

Porque, cuando se mira en la fuente re.py , (A + B) significa:

 h = re._compile('hello') # (D) h.match('hello world') 

y (C) es en realidad:

 re._compile('hello').match('hello world') 

Entonces, (C) no es lo mismo que (B). De hecho, (C) llama (B) después de llamar (D), que también es llamada por (A). En otras palabras, (C) = (A) + (B) . Por lo tanto, comparar (A + B) dentro de un bucle tiene el mismo resultado que (C) dentro de un bucle.

El regexTest.py prueba de George demostró esto para nosotros.

 noncompiled took 4.555 seconds. # (C) in a loop compiledInLoop took 4.620 seconds. # (A + B) in a loop compiled took 2.323 seconds. # (A) once + (B) in a loop 

El interés de todos es cómo obtener el resultado de 2.323 segundos. Para asegurarnos de que compile(...) solo sea llamado una vez, necesitamos almacenar el objeto regex comstackdo en la memoria. Si estamos utilizando una clase, podríamos almacenar el objeto y reutilizarlo cada vez que se llame a nuestra función.

 class Foo: regex = re.compile('hello') def my_function(text) return regex.match(text) 

Si no estamos utilizando la clase (que es mi solicitud de hoy), entonces no tengo ningún comentario. Todavía estoy aprendiendo a usar la variable global en Python, y sé que la variable global es algo malo.

Un punto más, creo que el uso de (A) + (B) tiene una ventaja. Aquí hay algunos datos que observé (corríjame si me equivoco):

  1. Llamadas A una vez, hará una búsqueda en el _cache seguido de una sre_compile.compile() para crear un objeto de expresión regular. Llamadas A dos veces, hará dos búsquedas y una comstackción (porque el objeto regex está en caché).

  2. Si el _cache se vacía, entonces el objeto regex se libera de la memoria y Python necesita comstackr nuevamente. (Alguien sugiere que Python no comstackrá).

  3. Si mantenemos el objeto de expresión regular utilizando (A), el objeto de expresión regular seguirá ingresando en _cache y se eliminará de alguna manera. Pero nuestro código mantiene una referencia en él y el objeto regex no se liberará de la memoria. Esos, Python no necesitan comstackr de nuevo.

  4. Las diferencias de 2 segundos en la prueba de George compiledInLoop vs compiled es principalmente el tiempo requerido para construir la clave y buscar el _cache. No significa el tiempo de comstackción de expresiones regulares.

  5. La prueba de realmente comstackción de George muestra qué sucede si realmente vuelve a hacer la comstackción cada vez: será 100 veces más lenta (redujo el bucle de 1.000.000 a 10.000).

Aquí están los únicos casos que (A + B) es mejor que (C):

  1. Si podemos almacenar en caché una referencia del objeto regex dentro de una clase.
  2. Si necesitamos llamar a (B) repetidamente (dentro de un bucle o varias veces), debemos almacenar en caché la referencia al objeto regex fuera del bucle.

Caso que (C) es suficientemente bueno:

  1. No podemos cachear una referencia.
  2. Solo lo usamos de vez en cuando.
  3. En general, no tenemos demasiadas expresiones regulares (supongamos que el comstackdo nunca se enjuaga)

Solo un resumen, aquí está el ABC:

 h = re.compile('hello') # (A) h.match('hello world') # (B) re.match('hello', 'hello world') # (C) 

Gracias por leer.

Sobre todo, hay poca diferencia si usas re.compile o no. Internamente, todas las funciones se implementan en términos de un paso de comstackción:

 def match(pattern, string, flags=0): return _compile(pattern, flags).match(string) def fullmatch(pattern, string, flags=0): return _compile(pattern, flags).fullmatch(string) def search(pattern, string, flags=0): return _compile(pattern, flags).search(string) def sub(pattern, repl, string, count=0, flags=0): return _compile(pattern, flags).sub(repl, string, count) def subn(pattern, repl, string, count=0, flags=0): return _compile(pattern, flags).subn(repl, string, count) def split(pattern, string, maxsplit=0, flags=0): return _compile(pattern, flags).split(string, maxsplit) def findall(pattern, string, flags=0): return _compile(pattern, flags).findall(string) def finditer(pattern, string, flags=0): return _compile(pattern, flags).finditer(string) 

Además, re.compile () omite la lógica adicional de indirección y almacenamiento en caché:

 _cache = {} _pattern_type = type(sre_compile.compile("", 0)) _MAXCACHE = 512 def _compile(pattern, flags): # internal: compile pattern try: p, loc = _cache[type(pattern), pattern, flags] if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE): return p except KeyError: pass if isinstance(pattern, _pattern_type): if flags: raise ValueError( "cannot process flags argument with a compiled pattern") return pattern if not sre_compile.isstring(pattern): raise TypeError("first argument must be string or compiled pattern") p = sre_compile.compile(pattern, flags) if not (flags & DEBUG): if len(_cache) >= _MAXCACHE: _cache.clear() if p.flags & LOCALE: if not _locale: return p loc = _locale.setlocale(_locale.LC_CTYPE) else: loc = None _cache[type(pattern), pattern, flags] = p, loc return p 

Además del pequeño beneficio de la velocidad del uso de re.compile , a las personas también les gusta la legibilidad que se obtiene al nombrar especificaciones de patrones potencialmente complejos y separarlas de la lógica de negocios donde se aplican:

 #### Patterns ############################################################ number_pattern = re.compile(r'\d+(\.\d*)?') # Integer or decimal number assign_pattern = re.compile(r':=') # Assignment operator identifier_pattern = re.compile(r'[A-Za-z]+') # Identifiers whitespace_pattern = re.compile(r'[\t ]+') # Spaces and tabs #### Applications ######################################################## if whitespace_pattern.match(s): business_logic_rule_1() if assign_pattern.match(s): business_logic_rule_2() 

Tenga en cuenta que otro encuestado creyó incorrectamente que los archivos pyc almacenaban patrones comstackdos directamente; sin embargo, en realidad se reconstruyen cada vez que se carga el PYC:

 >>> from dis import dis >>> with open('tmp.pyc', 'rb') as f: f.read(8) dis(marshal.load(f)) 1 0 LOAD_CONST 0 (-1) 3 LOAD_CONST 1 (None) 6 IMPORT_NAME 0 (re) 9 STORE_NAME 0 (re) 3 12 LOAD_NAME 0 (re) 15 LOAD_ATTR 1 (compile) 18 LOAD_CONST 2 ('[aeiou]{2,5}') 21 CALL_FUNCTION 1 24 STORE_NAME 2 (lc_vowels) 27 LOAD_CONST 1 (None) 30 RETURN_VALUE 

El desassembly anterior proviene del archivo PYC para un tmp.py contiene:

 import re lc_vowels = re.compile(r'[aeiou]{2,5}') 

En general, me parece que es más fácil usar banderas (al menos más fácil de recordar cómo), como re.I al comstackr patrones que usar banderas en línea.

 >>> foo_pat = re.compile('foo',re.I) >>> foo_pat.findall('some string FoO bar') ['FoO'] 

vs

 >>> re.findall('(?i)foo','some string FoO bar') ['FoO'] 

Usando los ejemplos dados:

 h = re.compile('hello') h.match('hello world') 

El método de coincidencia en el ejemplo anterior no es el mismo que se usa a continuación:

 re.match('hello', 'hello world') 

re.compile () devuelve un objeto de expresión regular , lo que significa que h es un objeto regex.

El objeto regex tiene su propio método de coincidencia con los parámetros pos y endpos opcionales:

regex.match(string[, pos[, endpos]])

pos

El segundo parámetro opcional pos da un índice en la cadena donde se debe iniciar la búsqueda; su valor predeterminado es 0. Esto no es completamente equivalente a cortar la cadena; el carácter del patrón '^' coincide en el comienzo real de la cadena y en las posiciones justo después de una nueva línea, pero no necesariamente en el índice donde se inicia la búsqueda.

endpos

El parámetro opcional endpos limita la distancia a la que se buscará la cadena; será como si la cadena tuviera una longitud de caracteres de endpos , por lo que solo se buscará una coincidencia en los caracteres de pos a endpos - 1 . Si endpos es menor que pos , no se encontrará ninguna coincidencia; de lo contrario, si rx es un objeto de expresión regular comstackdo, rx.search(string, 0, 50) es equivalente a rx.search(string[:50], 0) .

Los métodos de búsqueda , búsqueda y búsqueda de objetos de expresiones regulares también admiten estos parámetros.

re.match(pattern, string, flags=0) no los admite como puede ver,
ni tampoco sus contrapartes de búsqueda , búsqueda y búsqueda .

Un objeto coincidente tiene atributos que complementan estos parámetros:

match.pos

El valor de pos que se pasó al método search () o match () de un objeto regex. Este es el índice en la cadena en la que el motor de RE comenzó a buscar una coincidencia.

match.endpos

El valor de endpos que se pasó al método search () o match () de un objeto regex. Este es el índice en la cadena más allá de la cual el motor RE no irá.


Un objeto regex tiene dos atributos únicos, posiblemente útiles:

regex.grupos

El número de grupos de captura en el patrón.

regex.groupindex

Un diccionario que mapea cualquier nombre de grupo simbólico definido por (? P) para agrupar números. El diccionario está vacío si no se utilizaron grupos simbólicos en el patrón.


Y finalmente, un objeto de coincidencia tiene este atributo:

match.re

El objeto de expresión regular cuyo método match () o search () produjo esta instancia de coincidencia.

Hay una ventaja adicional de usar re.compile (), en la forma de agregar comentarios a mis patrones de expresiones regulares usando re.VERBOSE

 pattern = ''' hello[ ]world # Some info on my pattern logic. [ ] to recognize space ''' re.search(pattern, 'hello world', re.VERBOSE) 

Aunque esto no afecta la velocidad de ejecución de su código, me gusta hacerlo de esta manera, ya que es parte de mi hábito de comentar. No me gusta pasar el tiempo tratando de recordar la lógica que siguió a mi código 2 meses después cuando quiero hacer modificaciones.

Curiosamente, la comstackción es más eficiente para mí (Python 2.5.2 en Win XP):

 import re import time rgx = re.compile('(\w+)\s+[0-9_]?\s+\w*') str = "average 2 never" a = 0 t = time.time() for i in xrange(1000000): if re.match('(\w+)\s+[0-9_]?\s+\w*', str): #~ if rgx.match(str): a += 1 print time.time() - t 

Ejecutando el código anterior una vez como está, y una vez con las dos, if líneas comentaron al revés, la expresión regular comstackda es dos veces más rápida

Hice esta prueba antes de tropezar con la discusión aquí. Sin embargo, después de ejecutarlo, pensé que al menos publicaría mis resultados.

Robé y bastardé el ejemplo en “Mastering Regular Expressions” de Jeff Friedl. Esto es en un macbook con OSX 10.6 (2GHz intel core 2 duo, 4GB de RAM). La versión de Python es 2.6.1.

Ejecutar 1 – usando re.compile

 import re import time import fpformat Regex1 = re.compile('^(a|b|c|d|e|f|g)+$') Regex2 = re.compile('^[ag]+$') TimesToDo = 1000 TestString = "" for i in range(1000): TestString += "abababdedfg" StartTime = time.time() for i in range(TimesToDo): Regex1.search(TestString) Seconds = time.time() - StartTime print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds" StartTime = time.time() for i in range(TimesToDo): Regex2.search(TestString) Seconds = time.time() - StartTime print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds" Alternation takes 2.299 seconds Character Class takes 0.107 seconds 

Ejecutar 2 – No usar re.compile

 import re import time import fpformat TimesToDo = 1000 TestString = "" for i in range(1000): TestString += "abababdedfg" StartTime = time.time() for i in range(TimesToDo): re.search('^(a|b|c|d|e|f|g)+$',TestString) Seconds = time.time() - StartTime print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds" StartTime = time.time() for i in range(TimesToDo): re.search('^[ag]+$',TestString) Seconds = time.time() - StartTime print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds" Alternation takes 2.508 seconds Character Class takes 0.109 seconds 

Dejando a un lado la diferencia de rendimiento, usar re.compile y el objeto comstackdo de expresión regular para hacer coincidir (independientemente de las operaciones relacionadas con expresiones regulares) hace que la semántica sea más clara respecto del tiempo de ejecución de Python.

Tuve una experiencia dolorosa de depurar un código simple:

 compare = lambda s, p: re.match(p, s) 

y luego usaría comparar en

 [x for x in data if compare(patternPhrases, x[columnIndex])] 

donde se supone que patternPhrases es una variable que contiene una cadena de expresión regular, x[columnIndex] es una variable que contiene una cadena.

¡Tuve problemas para que las patternPhrases no coincidieran con alguna cadena esperada!

Pero si uso el formulario re.compile:

 compare = lambda s, p: p.match(s) 

entonces en

 [x for x in data if compare(patternPhrases, x[columnIndex])] 

Python se habría quejado de que “la cadena no tiene un atributo de coincidencia”, como por el mapeo de argumentos posicionales en compare , ¡ x[columnIndex] se usa como expresión regular !, cuando en realidad quise decir

 compare = lambda p, s: p.match(s) 

En mi caso, el uso de re.compile es más explícito del propósito de la expresión regular, cuando su valor está oculto a simple vista, por lo que podría obtener más ayuda de la verificación en tiempo de ejecución de Python.

Entonces, la moraleja de mi lección es que cuando la expresión regular no es solo una cadena literal, entonces debería usar re.compile para que Python me ayude a afirmar mi suposición.

Esta respuesta puede llegar tarde pero es un hallazgo interesante. Usar comstackción puede realmente ahorrarle tiempo si planea usar la expresión regular varias veces (esto también se menciona en los documentos). A continuación puede ver que el uso de un regex comstackdo es más rápido cuando se llama directamente al método de coincidencia. pasar una expresión regular comstackda a re.match lo hace aún más lento y pasar re.match con la cadena de patrones está en algún lugar en el medio.

 >>> ipr = r'\D+((([0-2][0-5]?[0-5]?)\.){3}([0-2][0-5]?[0-5]?))\D+' >>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re})) 1.5077415757028423 >>> ipr = re.compile(ipr) >>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re})) 1.8324008992184038 >>> average(*timeit.repeat("ipr.match('abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re})) 0.9187896518778871 

Además de la actuación.

Usar compile me ayuda a distinguir los conceptos de
1. módulo (re) ,
2. objeto regex
3. hacer coincidir el objeto
Cuando empecé a aprender regex

 #regex object regex_object = re.compile(r'[a-zA-Z]+') #match object match_object = regex_object.search('1.Hello') #matching content match_object.group() output: Out[60]: 'Hello' VS re.search(r'[a-zA-Z]+','1.Hello').group() Out[61]: 'Hello' 

Como complemento, hice una hoja de trucos exhaustiva del módulo para su referencia.

 regex = { 'brackets':{'single_character': ['[]', '.', {'negate':'^'}], 'capturing_group' : ['()','(?:)', '(?!)' '|', '\\', 'backreferences and named group'], 'repetition' : ['{}', '*?', '+?', '??', 'greedy vs lazy ?']}, 'lookaround' :{'lookahead' : ['(?=...)', '(?!...)'], 'lookbehind' : ['(?<=...)','(?...)', '(?P=name)', '(?:)'],}, 'escapes':{'anchor' : ['^', '\b', '$'], 'non_printable' : ['\n', '\t', '\r', '\f', '\v'], 'shorthand' : ['\d', '\w', '\s']}, 'methods': {['search', 'match', 'findall', 'finditer'], ['split', 'sub']}, 'match_object': ['group','groups', 'groupdict','start', 'end', 'span',] } 

Esta es una buena pregunta. A menudo ves que la gente usa re.compile sin razón. Disminuye la legibilidad. Pero seguro que hay muchas veces en que se requiere la precomstackción de la expresión. Como cuando lo usas repetidas veces en un bucle o algo así.

Es como todo sobre la progtwigción (todo en la vida en realidad). Aplicar el sentido común.

Realmente respeto todas las respuestas anteriores. De mi opinión ¡Sí! For sure it is worth to use re.compile instead of compiling the regex, again and again, every time.

Using re.compile makes your code more dynamic, as you can call the already compiled regex, instead of compiling again and aagain. This thing benefits you in cases:

  1. Processor Efforts
  2. Time Complexity.
  3. Makes regex Universal.(can be used in findall, search, match)
  4. And makes your program looks cool.

Example :

  example_string = "The room number of her room is 26A7B." find_alpha_numeric_string = re.compile(r"\b\w+\b") 

Using in Findall

  find_alpha_numeric_string.findall(example_string) 

Using in search

  find_alpha_numeric_string.search(example_string) 

Similarly you can use it for: Match and Substitute

(months later) it’s easy to add your own cache around re.match, or anything else for that matter —

 """ Re.py: Re.match = re.match + cache efficiency: re.py does this already (but what's _MAXCACHE ?) readability, inline / separate: matter of taste """ import re cache = {} _re_type = type( re.compile( "" )) def match( pattern, str, *opt ): """ Re.match = re.match + cache re.compile( pattern ) """ if type(pattern) == _re_type: cpat = pattern elif pattern in cache: cpat = cache[pattern] else: cpat = cache[pattern] = re.compile( pattern, *opt ) return cpat.match( str ) # def search ... 

A wibni, wouldn’t it be nice if: cachehint( size= ), cacheinfo() -> size, hits, nclear …

I’ve had a lot of experience running a compiled regex 1000s of times versus compiling on-the-fly, and have not noticed any perceivable difference

The votes on the accepted answer leads to the assumption that what @Triptych says is true for all cases. Esto no necesariamente es cierto. One big difference is when you have to decide whether to accept a regex string or a compiled regex object as a parameter to a function:

 >>> timeit.timeit(setup=""" ... import re ... f=lambda x, y: x.match(y) # accepts compiled regex as parameter ... h=re.compile('hello') ... """, stmt="f(h, 'hello world')") 0.32881879806518555 >>> timeit.timeit(setup=""" ... import re ... f=lambda x, y: re.compile(x).match(y) # compiles when called ... """, stmt="f('hello', 'hello world')") 0.809190034866333 

It is always better to compile your regexs in case you need to reuse them.

Note the example in the timeit above simulates creation of a compiled regex object once at import time versus “on-the-fly” when required for a match.

According to the Python documentation :

The sequence

 prog = re.compile(pattern) result = prog.match(string) 

es equivalente a

 result = re.match(pattern, string) 

but using re.compile() and saving the resulting regular expression object for reuse is more efficient when the expression will be used several times in a single program.

So my conclusion is if you are going to use the same match the same pattern for many different texts, you better precompile it.

Regular Expressions are compiled before being used when using the second version. If you are going to executing it many times it is definatly better to compile it first. If not compiling every time you match for one off’s is fine.

i’d like to motivate that pre-compiling is both conceptually and ‘literately’ (as in ‘literate programming’) advantageous. have a look at this code snippet:

 from re import compile as _Re class TYPO: def text_has_foobar( self, text ): return self._text_has_foobar_re_search( text ) is not None _text_has_foobar_re_search = _Re( r"""(?i)foobar""" ).search TYPO = TYPO() 

in your application, you’d write:

 from TYPO import TYPO print( TYPO.text_has_foobar( 'FOObar ) ) 

this is about as simple in terms of functionality as it can get. because this is example is so short, i conflated the way to get _text_has_foobar_re_search all in one line. the disadvantage of this code is that it occupies a little memory for whatever the lifetime of the TYPO library object is; the advantage is that when doing a foobar search, you’ll get away with two function calls and two class dictionary lookups. how many regexes are cached by re and the overhead of that cache are irrelevant here.

compare this with the more usual style, below:

 import re class Typo: def text_has_foobar( self, text ): return re.compile( r"""(?i)foobar""" ).search( text ) is not None 

In the application:

 typo = Typo() print( typo.text_has_foobar( 'FOObar ) ) 

I readily admit that my style is highly unusual for python, maybe even debatable. however, in the example that more closely matches how python is mostly used, in order to do a single match, we must instantiate an object, do three instance dictionary lookups, and perform three function calls; additionally, we might get into re caching troubles when using more than 100 regexes. also, the regular expression gets hidden inside the method body, which most of the time is not such a good idea.

be it said that every subset of measures—targeted, aliased import statements; aliased methods where applicable; reduction of function calls and object dictionary lookups—can help reduce computational and conceptual complexity.

My understanding is that those two examples are effectively equivalent. The only difference is that in the first, you can reuse the compiled regular expression elsewhere without causing it to be compiled again.

Here’s a reference for you: http://diveintopython3.ep.io/refactoring.html

Calling the compiled pattern object’s search function with the string ‘M’ accomplishes the same thing as calling re.search with both the regular expression and the string ‘M’. Only much, much faster. (In fact, the re.search function simply compiles the regular expression and calls the resulting pattern object’s search method for you.)