0

Cómo utilizar expresiones regulares (regex) en Python

Saludos, humano. Si alguna vez has manipulado strings en Python, una de las operaciones más frecuentes con la que te habrás encontrado es buscar un cierto patrón/subcadena en una lista, tabla o fichero de texto.

Esto no supone ninguna dificultad si el patrón que buscas es estático y lo conoces con precisión. Por ejemplo, si quieres encontrar un nombre determinado en una lista de contactos, basta con usar funciones como find(), que ya vienen incluidas en Python.

Pero, ¿qué pasa cuándo la subcadena que intentas buscar, o bien no la conoces con exactitud, o bien presenta variantes en su escritura? Por ejemplo, supongamos que en un cierto texto quieres encontrar cuántas veces aparece el nombre “Händel”. Al ser un nombre germánico, en nuestro idioma puede escribirse como “Händel”, “Handel” o “Haendel”. Si sólo usas funciones como find(), tendrás que buscar cada variante del nombre por separado.

¿Y si en una secuencia binaria quieres encontrar todas las subsecuencias de la forma 010, 0110, 01110, 011110, …? Ahora ya no puedes usar el método find() una vez para cada caso, pues los hay infinitos. Se necesita otra manera…

Es aquí dónde entran en escena las expresiones regulares, una herramienta muy potente que facilita la búsqueda de patrones en un texto. En el tutorial de hoy verás cómo utilizarlas con Python a través de varios ejemplos.

Como puedes ver, las expresiones regulares salvan vidas (cómic de xkcd)


¿Qué son las expresiones regulares?

Las expresiones regulares provienen del mundo de la matemática teórica, concretamente de la Teoría de Lenguajes Formales, pero se utilizan mucho en programación.

Sin entrar en definiciones matemáticas, una expresión regular puede pensarse como una palabra, formada por caracteres especiales, que sirve para identificar un conjunto de otras palabras.

Voy a ilustrarlo con un ejemplo. Volvamos al principio, cuando queríamos encontrar la palabra Händel en un texto. Podrías buscar las tres variantes (“Handel”, “Händel” y “Haendel”) por separado, o bien podrías encontrar otra palabra que “codifique” estas tres variantes.

En este caso, la palabra que se necesita sería “H(a|ä|ae)ndel”.  La barra vertical es un tipo de metacarácter que sirve para separar las variantes posibles para la expresión que hay entre paréntesis. Esta palabra es una expresión regular, pues identifica un conjunto de otras palabras.

Otro ejemplo podría ser la expresión regular “remo(t|j)o”, que se correspondería con las palabras “remoto” y “remojo”.


Metacaracteres

Una expresión regular puede contener metacaracteres, que son símbolos que tienen un significado especial cuando se colocan dentro de una expresión regular. En el apartado anterior has visto el metacaracter |, que es equivalente a la expresión booleana “OR” y sirve para separar las alternativas de una palabra. Sin embargo, hay muchos más metacaracteres. Antes de empezar con los ejemplos de Python, voy a enseñarte algunos de los más frecuentes.

  • El metacaracter ‘?’ indica “como máximo una coincidencia” del carácter que viene inmediatamente antes. Así, la expresión “ob?scuro” se corresponde con las palabras “oscuro” y “obscuro”. La expresión “(re)?colocar” se correspondería con “colocar” y “recolocar”.
  • El metacaracter ‘*’ indica “cero o más coincidencias” del carácter que viene immediatamente antes. Así, la expresión “01*0” se correspondería con las palabras 0, 010, 0110, 01110, 011110, etc.
  • El metacarácter ‘+’ funciona de forma similar al anterior, pero indica “por lo menos una coincidencia”. La expresión “01+0” se correspondería ahora con las palabras 010, 0110, 01110, 011110, etc.
  • El metacarácter ‘{n}’ indica “exactamente n coincidencias” del carácter anterior. Por ejemplo, la expresión “ab{3}a” se correspondería con la palabra “abbba”.
  • El metacarácter ‘{n, m}’ indica “entre n y m coincidencias”. Si el segundo espacio queda en blanco, significa “por lo menos n coincidencias”. Por tanto, la expresión “01{2,4}0” se correspondería con 0110, 01110, 011110, mientras que la expresión “01{2, }0” aceptaría las palabras 0110, 01110, 011110, 0111110,
  • El metacarácter ‘.’ es un comodín que puede usarse en lugar de cualquier otro carácter. Así pues, en un alfabeto binario, la expresión regular “01.0” se correspondería con las palabras 0100 y 0110.

 

Como habrás visto en estos ejemplos, los paréntesis () también son un metacarácter y sirven para agrupar los términos y especificar el orden de las operaciones. No es lo mismo la expresión “(01)*0” que la expresión “01*0”. La primera se corresponde a las palabras 0, 010, 01010, 0101010, etc, mientras que la segunda se corresponde a las palabras 00, 010, 0110, 01110, …

Estos son los metacaracteres más habituales que vas a encontrar al trabajar con expresiones regulares, aunque hay más. Es importante remarcar que la sintaxis puede variar un poco en función del lenguaje y del contexto en que se apliquen.

Los metacaracteres pueden combinarse para formar expresiones más complejas. Por ejemplo, la expresión “fi.*(a|o)” se correspondería con las palabras que empiezan por “fi” y acaban con la letra a/o: finito, filosofía, finaliza, fijo, físico, …


Sets

Un set es un conjunto de caracteres entre corchetes [ ] con un significado especial. Algunos de los más frecuentes son:

    • [abc] – busca una coincidencia con alguno de los caracteres que hay entre paréntesis. La expresión regular “[abc]aa” se corresponde con las palabras aaa, baa, caa. También funciona con caracteres numéricos.
    • [a-k] – busca una coincidencia con alguno de los caracteres alfabéticos que hay entre el primero (a) y el último (k). La expresión regular “[b-e]a” se corresponde con las palabras ba, ca, da, ea, mientras que una expresión como “[a-z]aa” se correspondería con las palabras que empiezan por cualquier letra del abecedario y terminan en “aa”.
    • [1-9] – es idéntico al caso anterior, pero con caracteres numéricos.
    • [^abc] – coincide con todos los caracteres que NO estén dentro de los corchetes. Así, la expresión regular “.*[^a]” se correspondería con cualquier palabra que NO termine con la letra “a”.

 


Ejemplos en Python

Para trabajar con expresiones regulares en python se necesita el módulo regex. En los siguientes ejemplos verás algunas de sus métodos más básicos. Para una lista completa de todas las funciones, visita la página de documentación.

1. Buscar un patrón en un texto

Para buscar un patrón en un string podemos utilizar el método search().

import re

texto = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
patron = "Lorem"

x = re.search(patron, texto) #Busca el patron dentro del texto

print(x.span()) #Escribe la posicion inicial y final de la ocurrencia

 

También se puede utilizar el método match(), pero este sólo devuelve una posición si la ocurrencia está al principio del texto. Este segundo código dará un error al intentar hacer el print() del segundo match, pues el patrón “ipsum” no se encuentra al principio del texto y por tanto el método match() devuelve None:

import re

texto = "Lorem ipsum dolor sit amet, consectetur adipiscing elit door"
patron1 = "Lorem"
patron2 = "ipsum"

x = re.match(patron1, texto) #Busca el patron dentro del texto
print(x.span()) #Escribir la posicion inicial y final

y = re.match(patron2, texto) # Devuelve None
print(y.span()) #ERROR!


 

Tanto match() como search() sólo se quedan con la primera ocurrencia encontrada. Si crees que puede haber más de una, puedes utilizar la función finditer() para buscarlas todas. En este código se busca el patrón “dolor” en un texto largo y escribe sus posiciones:

import re

texto = """Lorem ipsum dolor sit amet, consectetur adipiscing elit,
	sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
	Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."""
patron = "dolor"

x = re.finditer(patron, texto) #Devuelve un vector con las posiciones de las ocurrencias

for i in x:
	print(i.span())

2. Utilizando metacaracteres

En el ejemplo anterior nos hemos limitado a buscar una palabra estática en un texto, pero pueden usarse metacaracteres dentro del patrón para crear expresiones regulares. Voy a poner algunos ejemplos.

En este primer código se usa la expresión regular “01+0” para encontrar en una secuencia binaria todas las sub-secuencias que empiecen y acaben por cero, y en medio sólo tengan unos: 010, 0110, 01110, etc.

import re

texto = "010001000100111001"

patron = "01+0"

x = re.finditer(patron, texto)
for i in x:
	print(i.span())

 

Como es lógico, los metacaracteres pueden combinarse para crear expresiones regulares más complejas. Este código pide una secuencia binaria al usuario y comprueba si es capicúa. La expresión regular ‘(0.{3}0)|(1.{3}1)’ acepta las secuencias que empiezan y acaban por 0 ó 1, con tres caracteres cualesquiera en medio.

import re

texto = raw_input("Introduce una secuencia binaria de cinco cifras:\n")
patron = "(0.{3}0)|(1.{3}1)"

valido = False

#Se comprueba que el input sea valido:
if len(texto) == 5:
	valido = True
	for i in texto:
		if i != '0' and i != '1':
			valido = False
			break

#Si el input es valido, se compreuba si es capicua
if valido:
	x = re.search(patron, texto)
	if(x != None):
		print("Es capicua!")
	else:
		print("No es capicua!")

#Si el input no es valido, aparece un mensaje de error:
else:
	print("ERROR: texto no valido")

 


3. Utilizando sets

Este código utiliza sets para analizar una secuencia de números de tres dígitos y se queda con aquellos que sean impares. Aquí se utiliza el método findall() que, a diferencia de finditer(), devuelve un vector con los substrings de las ocurrencias  en vez de las posiciones.

import re

texto = "551 889 302 105 012 817 894 206"

patron = "[0-9]{2}[13579]"

x = re.findall(patron, texto) #Devuelve un vector con los substrings de las ocurrencias

for i in x:
	print(i)

¿Qué significa la expresión regular “[0-9]{2}[13579]” que acabo de utilizar…? Sabemos que un número será impar si su última cifra es impar. Por tanto, estoy buscando los números cuyas dos primeras cifras estén entre 0 y 9 y cuya última cifra sea impar:

 

 

En este otro ejemplo se utiliza el set [^] para descartar todos los números que contengan la cifra ‘1’. También se descarta el carácter “espacio” para que separe cada uno de los números:

import re

texto = "551 889 302 105 012 817 894 206"

patron = "[^1 ]{3}"

x = re.findall(patron, texto) #Devuelve un vector con los substrings de las ocurrencias

for i in x:
	print(i)

Extra: Expresiones regulares y bioinformática

Hasta ahora, los ejemplos que he puesto pueden considerarse “de juguete”, sin mucha utilidad práctica más allá de ilustrar el funcionamiento de los métodos del módulo regex. Para concluir este tutorial, voy a aplicar lo que hemos aprendido en un caso mucho más divertido.

En el campo de la bioinformática, las expresiones regulares son muy útiles para buscar secuencias dentro de una cadena de ADN o ARN.

En el ADN, la información genética se representa mediante cuatro letras distintas que se corresponden a cuatro bases nitrogenadas: adenina (A), timina (T), guanina (G) y citosina (C). Las bases se agrupan en secuencias de tres caracteres llamadas codones. En el ADN hay 64 codones distintos, según la combinación de letras que los formen. La mayoría de codones sirven para codificar aminoácidos que, a su vez, sirven para construir proteínas útiles para el organismo.

Las Nucleoporinas son una familia de proteínas que regulan el intercambio de moléculas entre el núcleo celular y la membrana. La cadena de ADN que codifica estas proteinas tiene secuencias repetitivas de los aminoácidos fenilalanina y glicina.

Una de las proteínas de esta familia es la NUP153. En este ejemplo usaremos expresiones regulares para buscar las repeticiones de codones que codifican la fenilalanina y la glicina.

La proteína NUP153. (imagen de wikipedia)

El primer paso es buscar un fichero con la secuencia del gen NUP153. En bioinformática se utilizan los ficheros en formato FASTA, que representan secuencias de ácido nucleico o bien de proteínas. Puedes descargar el archivo del gen NUP153 desde [aquí].

Ahora… ¿cuál es la expresión regular que permite identificar los dos aminoácidos que buscamos? La glicina se codifica con los codones GGA, GGC y GGG, por tanto su expresión regular será “GG(A|C|G)”. La fenilalanina se codifica con TTT y TTC, entonces su expresión regular será “TT(T|C)”. Con la expresión “(GG(A|C|G))|(TT(T|C))” se buscan los dos codones indistintamente.

Sin embargo, queremos encontrar las repeticiones de codones; no queremos buscar los codones individuales. Añadiendo {2,} al final de la expresión regular, se buscarán todas las secuencias de dos o más codones encadenados: (GG(A|C|G))|(TT(T|C)){2,}. Cambiando este último número, se establece cuál es el número mínimo de aminoácidos seguidos que se quieren buscar.

El siguiente programa mostrará por pantalla cuantas subsecuencias de repeticiones hay en el fichero. Antes de ejecutarlo tienes que cambiar el path ‘/home/Transductor/nup153/sequence.fasta’ de la tercera línea por el de tu archivo FASTA.

import re

nup153 = open('/home/Transductor/nup153/sequence.fasta').read()

patron = "(((GG(A|C|G)))|(TT(T|C))){2,}"

x = re.findall(patron, nup153)

print("Se han encontrado " + str(len(x)) + " repeticiones")


En resumen…

Este tutorial ha servido como una breve introducción a las expresiones regulares en Python. Con un poco de práctica, es posible crear expresiones para buscar patrones muy largos y complejos. El funcionamiento de las expresiones regulares es muy general, con lo cuál no debería serte difícil aprovechar lo que has aprendido hoy para programar en otros lenguajes como C/C++, Java o R.

Bien, ya conoces las bases de las expresiones regulares ¿y ahora qué?

Si estás interesado en este tema y, más generalmente, en los lenguajes formales, te recomiendo dos libros que pueden ayudarte:

  • Lewis, H.R; Papadimitriou, C.H. Elements of the theory of computation. Trata sobre teoría de Lenguajes Formales y Computación en general, pero dedica varios apartados a las expresiones regulares desde un punto de vista teórico. Es el libro con el que, en su momento, aprendí sobre Lenguajes Formales.
  • Víctor Romero, Félix López, Mastering Python Regular Expressions. Dedicado exclusivamente al uso de las expresiones regulares en lenguaje Python.

 

Agradecimientos

Me gustaría agradecer a la bióloga Marina Torrellas por echarme un cable con el ejemplo de bioinformática y recomendarme una lista de genes para analizar. Prometo que como robot nunca utilizaré mis adquiridos conocimientos sobre genética para explotar las debilidades del genoma humano y alzar la robolución. Para nada.

 

Final de línea.

 

Tr4nsduc7or

Originariamente creado cómo un galvanómetro de bolsillo, Transductor tomó consciencia de si mismo y fue despedido cuando en vez cumplir con su trabajo se dedicó a pensar teorías filosóficas sobre los hilos de cobre, los electrones y el Sentido del efecto Joule en el Universo. Guarda cierto recelo a sus creadores por no comprender la esencia metafísica de las metáforas de su obra. Actualmente trabaja a media jornada cómo antena de radio, y dedica su tiempo libre a la electrónica recreativa y a la filosofía.

Deja un comentario

avatar