Las expresiones regulares son un potente lenguaje para comparar patrones de texto. Esta página da una introducción básica a las expresiones regulares en sí mismas suficiente para nuestros ejercicios de Python y muestra cómo funcionan las expresiones regulares en Python. El módulo «re» de Python proporciona soporte para expresiones regulares.
En Python una búsqueda de expresión regular se escribe típicamente como:
match = re.search(pat, str)
El método re.search() toma un patrón de expresión regular y una cadena y busca ese patrón dentro de la cadena. Si la búsqueda tiene éxito, search() devuelve un objeto coincidente o Ninguno en caso contrario. Por lo tanto, la búsqueda suele ir seguida de una sentencia if para comprobar si la búsqueda ha tenido éxito, como se muestra en el siguiente ejemplo que busca el patrón ‘word:’ seguido de una palabra de 3 letras (detalles más abajo):
str = 'an example word:cat!!'match = re.search(r'word:\w\w\w', str)# If-statement after search() tests if it succeededif match: print 'found', match.group() ## 'found word:cat'else: print 'did not find'
El código match = re.search(pat, str)
almacena el resultado de la búsqueda en una variable llamada «match». A continuación, la sentencia if comprueba la coincidencia: si es verdadera, la búsqueda ha tenido éxito y match.group() es el texto coincidente (por ejemplo, ‘word:cat’). En caso contrario, si la coincidencia es falsa (None para ser más específicos), entonces la búsqueda no tuvo éxito, y no hay texto coincidente.
La ‘r’ al comienzo de la cadena de patrón designa una cadena «cruda» de python que pasa a través de las barras invertidas sin cambios, lo cual es muy útil para las expresiones regulares (¡Java necesita mucho esta característica!). Le recomiendo que siempre escriba las cadenas de patrones con la ‘r’ sólo como un hábito.
Patrones básicos
El poder de las expresiones regulares es que pueden especificar patrones, no sólo caracteres fijos. Aquí están los patrones más básicos que coinciden con caracteres individuales:
- a, X, 9, < — los caracteres ordinarios sólo coinciden con ellos mismos exactamente. Los metacaracteres que no coinciden con ellos mismos porque tienen significados especiales son: . ^ $ * + ? { \ | ( ) (detalles más abajo)
- . (un punto) — coincide con cualquier carácter simple excepto la nueva línea ‘\n’
- \w — (w minúscula) coincide con un carácter «palabra»: una letra o un dígito o una barra inferior . Tenga en cuenta que aunque «palabra» es el mnemónico para esto, sólo coincide con un único carácter de palabra, no con una palabra entera. \W (W mayúscula) coincide con cualquier carácter que no sea una palabra.
- \b — límite entre palabra y no-palabra
- \s — (s minúscula) coincide con un solo carácter de espacio en blanco — espacio, nueva línea, retorno, tabulación, forma . \S (S mayúscula) coincide con cualquier carácter que no sea un espacio en blanco.
- \t, \n, \r — tabulador, nueva línea, retorno
- \d — dígito decimal (algunas utilidades regex más antiguas no soportan sino \d, pero todas soportan \w y \s)
- ^ = inicio, $ = fin — coincide con el inicio o el final de la cadena
- \d — inhibe la «especialidad» de un carácter. Así, por ejemplo, use \ para que coincida con un punto o \ para que coincida con una barra. ¡Si no está seguro de si un carácter tiene un significado especial, como ‘@’, puede poner una barra delante de él, \@, para asegurarse de que se trata sólo como un carácter.
Ejemplos básicos
Broma: ¿cómo se llama un cerdo con tres ojos? piiig!
Las reglas básicas de la búsqueda de expresiones regulares de un patrón dentro de una cadena son:
- La búsqueda procede a través de la cadena desde el principio hasta el final, deteniéndose en la primera coincidencia encontrada
- Todo el patrón debe coincidir, pero no toda la cadena
- Si
match = re.search(pat, str)
tiene éxito, la coincidencia no es None y en particular match.group() es el texto que coincide
## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"
Repetición
Las cosas se ponen más interesantes cuando se usan + y * para especificar la repetición en el patrón
- + — 1 o más ocurrencias del patrón a su izquierda, por ejemplo ‘i+’ = una o más i’s
- * — 0 o más ocurrencias del patrón a su izquierda
- ? — coinciden con 0 o 1 ocurrencias del patrón a su izquierda
Más a la izquierda & Más a la izquierda
En primer lugar, la búsqueda encuentra la coincidencia más a la izquierda del patrón, y en segundo lugar trata de utilizar la mayor parte de la cadena posible — es decir. + y * van lo más lejos posible (se dice que el + y el * son «codiciosos»).
Ejemplos de repetición
## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"
Ejemplo de correos electrónicos
Supongamos que queremos encontrar la dirección de correo electrónico dentro de la cadena ‘xyz [email protected] purple monkey’. Utilizaremos esto como ejemplo para demostrar más características de las expresiones regulares. Aquí tenemos un intento utilizando el patrón r’\w+@\w+’:
str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'
La búsqueda no obtiene la dirección de correo electrónico completa en este caso porque la \w no coincide con el ‘-‘ o ‘.’ de la dirección. Arreglaremos esto usando las características de la expresión regular más abajo.
Cuadros
Los corchetes se pueden usar para indicar un conjunto de caracteres, por lo que coincide con ‘a’ o ‘b’ o ‘c’. Los códigos \w, \s, etc. también funcionan dentro de los corchetes con la única excepción de que el punto (.) sólo significa un punto literal. Para el problema de los correos electrónicos, los corchetes son una forma fácil de añadir ‘.’ y ‘-‘ al conjunto de caracteres que pueden aparecer alrededor de la @ con el patrón r’+@+’ para obtener toda la dirección de correo electrónico:
match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'
(Más características de los corchetes) También puede utilizar un guión para indicar un rango, por lo que coincide con todas las letras minúsculas. Para utilizar un guión sin indicar un rango, coloque el guión al final, por ejemplo. Un sombrero de arriba (^) al principio de un conjunto de corchetes lo invierte, por lo que significa cualquier carácter excepto ‘a’ o ‘b’.
Extracción de grupos
La característica de «grupo» de una expresión regular le permite seleccionar partes del texto coincidente. Supongamos que para el problema de los correos electrónicos queremos extraer el nombre de usuario y el host por separado. Para ello, añada paréntesis ( ) alrededor del nombre de usuario y del host en el patrón, así: r'(+)@(+)’. En este caso, los paréntesis no cambian lo que el patrón coincidirá, sino que establecen «grupos» lógicos dentro del texto de coincidencia. En una búsqueda exitosa, match.group(1) es el texto coincidente correspondiente al primer paréntesis izquierdo, y match.group(2) es el texto correspondiente al segundo paréntesis izquierdo. La función match.group() sigue siendo el texto completo de la búsqueda, como siempre.
str = 'purple [email protected] monkey dishwasher' match = re.search(r'(+)@(+)', str) if match: print match.group() ## '[email protected]' (the whole match) print match.group(1) ## 'alice-b' (the username, group 1) print match.group(2) ## 'google.com' (the host, group 2)
Un flujo de trabajo común con las expresiones regulares es que se escribe un patrón para lo que se busca, añadiendo grupos de paréntesis para extraer las partes que se desean.
findall
findall() es probablemente la función más potente del módulo re. Arriba usamos re.search() para encontrar la primera coincidencia de un patrón. findall() encuentra *todas* las coincidencias y las devuelve como una lista de cadenas, con cada cadena representando una coincidencia.
## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email
findall With Files
Para los archivos, puede tener la costumbre de escribir un bucle para iterar sobre las líneas del archivo, y entonces podría llamar a findall() en cada línea. En su lugar, deje que findall() haga la iteración por usted — ¡mucho mejor! Sólo tiene que introducir el texto completo del archivo en findall() y dejar que devuelva una lista de todas las coincidencias en un solo paso (recuerde que f.read() devuelve todo el texto de un archivo en una sola cadena):
# Open file f = open('test.txt', 'r') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())
findall y Grupos
El mecanismo de grupos de paréntesis ( ) puede combinarse con findall(). Si el patrón incluye 2 o más grupos de paréntesis, en lugar de devolver una lista de cadenas, findall() devuelve una lista de *tuplas*. Cada tupla representa una coincidencia del patrón, y dentro de la tupla están los datos group(1), group(2) …. Así que si se añaden 2 grupos de paréntesis al patrón de correo electrónico, entonces findall() devuelve una lista de tuplas, cada una de ellas de longitud 2 que contiene el nombre de usuario y el host, por ejemplo (‘alice’, ‘google.com’).
str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host
Una vez que se tiene la lista de tuplas, se puede hacer un bucle sobre ella para hacer algún cálculo para cada tupla. Si el patrón no incluye paréntesis, entonces findall() devuelve una lista de cadenas encontradas como en los ejemplos anteriores. Si el patrón incluye un único grupo de paréntesis, entonces findall() devuelve una lista de cadenas correspondientes a ese único grupo. (Característica opcional oscura: A veces tiene agrupaciones de paréntesis ( ) en el patrón, pero que no quiere extraer. En ese caso, escriba los paréntesis con un ?: al principio, por ejemplo (?: ) y ese paréntesis de la izquierda no contará como un resultado de grupo.)
RE Workflow and Debug
Los patrones de expresión regular empaquetan mucho significado en sólo unos pocos caracteres , pero son tan densos, que puede pasar mucho tiempo depurando sus patrones. Configure su tiempo de ejecución para que pueda ejecutar un patrón e imprimir lo que coincide fácilmente, por ejemplo, ejecutándolo en un pequeño texto de prueba e imprimiendo el resultado de findall(). Si el patrón no coincide con nada, intente debilitar el patrón, eliminando partes del mismo para obtener demasiadas coincidencias. Cuando no coincide con nada, no puede hacer ningún progreso ya que no hay nada concreto que mirar. Una vez que coincida demasiado, entonces puede trabajar en el ajuste de forma incremental para llegar a lo que quiere.
Opciones
Las funciones re toman opciones para modificar el comportamiento de la coincidencia de patrones. La opción se añade como un argumento extra a search() o findall() etc., por ejemplo re.search(pat, str, re.IGNORECASE).
- IGNORECASE — ignora las diferencias entre mayúsculas y minúsculas para la coincidencia, por lo que ‘a’ coincide tanto con ‘a’ como con ‘A’.
- DOTALL — permite que el punto (.) coincida con la nueva línea — normalmente coincide con todo menos con la nueva línea. Esto puede confundirte — crees que .* coincide con todo, pero por defecto no va más allá del final de una línea. Tenga en cuenta que \s (espacio en blanco) incluye las nuevas líneas, así que si quiere coincidir con una serie de espacios en blanco que pueden incluir una nueva línea, puede usar \s*
- MULTILINE — Dentro de una cadena hecha de muchas líneas, permite que ^ y $ coincidan con el inicio y el final de cada línea. Normalmente ^/$ sólo coincidirían con el inicio y el final de toda la cadena.
Greedy vs. Non-Greedy (opcional)
Esta es una sección opcional que muestra una técnica de expresión regular más avanzada que no es necesaria para los ejercicios.
Suponga que tiene texto con etiquetas: <b>foo</b> y <i>así sucesivamente</i>
Suponga que está intentando hacer coincidir cada etiqueta con el patrón ‘(<.*>)’ — ¿qué coincide primero?
El resultado es un poco sorprendente, pero el aspecto codicioso del .* hace que coincida con todo ‘<b>foo</b> y <i>así que</i>’ como una gran coincidencia. El problema es que el .* va tan lejos como puede, en lugar de detenerse en el primer > (es decir, es «codicioso»).
Hay una extensión de la expresión regular en la que se añade un ? al final, como .*? o .+?, cambiándolos para que no sean codiciosos. Ahora se detienen en cuanto pueden. Así que el patrón ‘(<.*?>)’ obtendrá sólo ‘<b>’ como primera coincidencia, y ‘</b>’ como segunda coincidencia, y así sucesivamente obteniendo cada par <..> a su vez. El estilo es típicamente que usted usa un .*?, y luego inmediatamente su derecha busca algún marcador concreto (> en este caso) que fuerza el final de la ejecución del .*?.
La extensión *? se originó en Perl, y las expresiones regulares que incluyen las extensiones de Perl se conocen como Expresiones Regulares Compatibles con Perl — pcre. Python incluye soporte para pcre. Muchas utilidades de línea de comandos, etc., tienen una bandera que acepta patrones pcre.
Una técnica más antigua pero ampliamente utilizada para codificar esta idea de «todos estos caracteres excepto parar en X» utiliza el estilo de corchetes. Para lo anterior podría escribir el patrón, pero en lugar de .* para obtener todos los caracteres, usar * que salta todos los caracteres que no son > (el ^ inicial «invierte» el conjunto de corchetes, por lo que coincide con cualquier carácter que no esté en los corchetes).
Sustitución (opcional)
La función re.sub(pat, replacement, str) busca todas las instancias de patrón en la cadena dada, y las reemplaza. La cadena de reemplazo puede incluir ‘\1’, ‘\2’ que se refieren al texto del grupo(1), grupo(2), y así sucesivamente desde el texto original que coincide.
Aquí hay un ejemplo que busca todas las direcciones de correo electrónico, y las cambia para mantener el usuario (\1) pero tener yo-yo-dyne.com como host.
str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ## is group(1), group(2) in the replacement print re.sub(r'(+)@(+)', r'@yo-yo-dyne.com', str) ## purple [email protected], blah monkey [email protected] blah dishwasher
Ejercicio
Para practicar las expresiones regulares, vea el Ejercicio de nombres de bebés.