Python: Expresiones regulares

Las expresiones regulares, también llamadas regex o regexp, consisten en patrones que describen conjuntos de cadenas de caracteres.

Algo parecido sería escribir en la línea de comandos de Windows

dir *.exe

‘*.exe’ sería una “expresión regular” que describiría todas las cadenas de caracteres que empiezan con cualquier cosa seguida de ‘.exe’, es decir, todos los archivos exe.

El trabajo con expresiones regulares en Python se realiza mediante el módulo re, que data de Python 1.5 y que proporciona una sintaxis para la creación de patrones similar a la de Perl. En Python 1.6 el módulo se reescribió para dotarlo de soporte de cadenas unicode y mejorar su rendimiento.

El módulo re contiene funciones para buscar patrones dentro de una cadena (search), comprobar si una cadena se ajusta a un determinado criterio descrito mediante un patrón (match), dividir la cadena usando las ocurrencias del patrón como puntos de ruptura (split) o para sustituir todas las ocurrencias del patrón por otra cadena (sub). Veremos estas funciones y alguna más en la próxima sección, pero por ahora, aprendamos algo más sobre la sintaxis de las expresiones regulares.

Expresiones regulares: Patrones

La expresión regular más sencilla consiste en una cadena simple, que describe un conjunto compuesto tan solo por esa misma cadena. Por ejemplo, veamos cómo la cadena “python” coincide con la expresión regular “python” usando la función match:

import re

if re.match("python", "python"):
   print "cierto"

Si quisiéramos comprobar si la cadena es python, jython, cython o cualquier otra cosa que termine en “ython”, podríamos utilizar el carácter comodín, el punto ‘.’:

re.match(".ython", "python")
re.match(".ython", "jython")

La expresión regular “.ython” describiría a todas las cadenas que consistan en un carácter cualquiera, menos el de nueva línea, seguido de “ython”. Un carácter cualquiera y solo uno. No cero, ni dos, ni tres.

En el caso de que necesitáramos el carácter ‘.’ en la expresión regular, o cualquier otro de los caracteres especiales que veremos a continuación, tendríamos que escaparlo utilizando la barra invertida.

Para comprobar si la cadena consiste en 3 caracteres seguidos de un punto, por ejemplo, podríamos utilizar lo siguiente:

re.match("...\.", "abc.")

Si necesitáramos una expresión que sólo resultara cierta para las cadenas “python”, “jython” y “cython” y ninguna otra, podríamos utilizar el carácter ‘|’ para expresar alternativa escribiendo los tres subpatrones completos:

re.match("python|jython|cython", "python")

o bien tan solo la parte que pueda cambiar, encerrada entre paréntesis, formando lo que se conoce como un grupo. Los grupos tienen una gran importancia a la hora de trabajar con expresiones regulares y este no es su único uso, como veremos en la siguiente sección.

re.match("(p|j|c)ython", "python")

Otra opción consistiría en encerrar los caracteres ‘p’, ‘j’ y ‘c’ entre corchetes para formar una clase de caracteres, indicando que en esa posición puede colocarse cualquiera de los caracteres de la clase.

re.match("[pjc]ython", "python")

¿Y si quisiéramos comprobar si la cadena es python0, python1, python2, … , python9? En lugar de tener que encerrar los 10 dígitos dentro de los corchetes podemos utilizar el guión, que sirve para indicar rangos. Por ejemplo a-d indicaría todas las letras minúsculas de la ‘a’ a la ‘d’; 0-9 serían todos los números de 0 a 9 inclusive.

re.match("python[0-9]", "python0")

Si quisiéramos, por ejemplo, que el último carácter fuera o un dígito o una letra simplemente se escribirían dentro de los corchetes todos los criterios, uno detras de otro.

re.match("python[0-9a-zA-Z]", "pythonp")

Es necesario advertir que dentro de las clases de caracteres los caracteres especiales no necesitan ser escapados. Para comprobar si la cadena es “python.” o “python,”, entonces, escribiríamos:

re.match("python[.,]", "python.")

y no

re.match("python[\.,]", "python.")

ya que en este último caso estaríamos comprobando si la cadena es “python.”, “python,” o “python\”.

Los conjuntos de caracteres también se pueden negar utilizando el símbolo ‘^’. La expresión “python[^0-9a-z]“, por ejemplo, indicaría que nos interesan las cadenas que comiencen por “python” y tengan como último carácter algo que no sea ni una letra minúscula ni un número.

re.match("python[^0-9a-z]", "python+")

El uso de [0-9] para referirse a un dígito no es muy común, ya que, al ser la comprobación de que un carácter es un dígito algo muy utilizado, existe una secuencia especial equivalente: ‘\d’. Existen otras secuencias disponibles que listamos a continuación:

  • \d

    un dígito. Equivale a [0-9]

  • \D

    cualquier carácter que no sea un dígito. Equivale a [^0-9]

  • \w

    Cualquier caracter alfanumérico. Equivale a [a-zA-Z0-9_].

  • \W

    Cualquier carácter no alfanumérico. Equivale a [^a-zA-Z0-9_].

  • \s

    Cualquier carácter en blanco. Equivale a [ \t\n\r\f\v]

  • \S

    Cualquier carácter que no sea un espacio en blanco. Equivale a [^ \t\n\r\f\v]

Veamos ahora cómo representar repeticiones de caracteres, dado que no sería de mucha utilidad tener que, por ejemplo, escribir una expresión regular con 30 caracteres ‘\d’ para buscar números de 30 dígitos. Para este menester tenemos los caracteres especiales ‘+’, ‘*’, ‘?”, además de las llaves ‘{}’.

El carácter ‘+’ indica que lo que tenemos a la izquierda, sea un carácter como ‘a’, una clase como ‘[abc]’ o un subpatrón como (abc), puede encontrarse una o mas veces. Por ejemplo la expresión regular “python+” describiría las cadenas “python”, “pythonn”, “pythonnn”, pero no “pytho”, ya que debe haber al menos una n.

El carácter ‘*’ es similar a ‘+’, pero en este caso lo que se sitúa a su izquierda puede encontrarse cero o mas veces.

El carácter ‘?’ indica opcionalidad, es decir, lo que tenemos a la izquierda puede o no aparecer (puede aparecer 0 o 1 veces).

Finalmente las llaves sirven para indicar el número de veces exacto que puede aparecer el carácter de la izquierda, o bien un rango de veces que puede aparecer. Por ejemplo {3} indicaría que tiene que aparecer exactamente 3 veces, {3,8} indicaría que tiene que aparecer de 3 a 8 veces, {,8} de 0 a 8 veces y {3,} tres veces o mas (las que sean).

Otro elemento interesante en las expresiones regulares, para terminar, es la especificación de las posiciones en que se tiene que encontrar la cadena, esa es la utilidad de ^ y $, que indican que el elemento sobre el que actúa debe ir al principio de la cadena o al final de esta.

La cadena “http://mundogeek.net”, por ejemplo, se ajustaría a la expresión regular “^http”, mientras que la cadena “El protocolo es http” no lo haría, ya que el http no se encuentra al principio de la cadena.

Expresiones regulares: Usando el módulo re

Ya hemos visto por encima cómo se utiliza la función match del módulo re para comprobar si una cadena se ajusta a un determinado patrón. El primer parámetro de la función es la expresión regular, el segundo, la cadena a comprobar y existe un tercer parámetro opcional que contiene distintos flags que se pueden utilizar para modificar el comportamiento de las expresiones regulares.

Algunos ejemplos de flags del módulo re son re.IGNORECASE, que hace que no se tenga en cuenta si las letras son mayúsculas o minúsculas o re.VERBOSE, que hace que se ignoren los espacios y los comentarios en la cadena que representa la expresión regular.

El valor de retorno de la función será None en caso de que la cadena no se ajuste al patrón o un objeto de tipo MatchObject en caso contrario. Este objeto MatchObject cuenta con métodos start y end que devuelven la posición en la que comienza y finaliza la subcadena reconocida y métodos group y groups que permiten acceder a los grupos que propiciaron el reconocimiento de la cadena.

Al llamar al método group sin parámetros se nos devuelve el grupo 0 de la cadena reconocida. El grupo 0 es la subcadena reconocida por la expresión regular al completo, aunque no existan paréntesis que delimiten el grupo.

>>> mo = re.match(“http://.+\net”, “http://mundogeek.net”)
>>> print mo.group()
http://mundogeek.net

Podríamos crear grupos utilizando los paréntesis, como aprendimos en la sección anterior, obteniendo así la parte de la cadena que nos interese.

>>> mo = re.match(“http://(.+)\net”, “http://mundogeek.net”)
>>> print mo.group(0)
http://mundogeek.net
>>> print mo.group(1)
mundogeek

El método groups, por su parte, devuelve una lista con todos los grupos, exceptuando el grupo 0, que se omite.

>>> mo = re.match(“http://(.+)\(.{3})”, “http://mundogeek.net”)
>>> print mo.groups()
(‘mundogeek’, ‘net’)

La función search del módulo re funciona de forma similar a match; contamos con los mismos parámetros y el mismo valor de retorno. La única diferencia es que al utilizar match la cadena debe ajustarse al patrón desde el primer carácter de la cadena, mientras que con search buscamos cualquier parte de la cadena que se ajuste al patrón. Por esta razón el método start de la función match siempre devolverá 0, mientras que en el caso de search esto no tiene por qué ser así.

Otra función de búsqueda del módulo re es findall. Este toma los mismos parámetros que las dos funciones anteriores, pero devuelve una lista con las subcadenas que cumplieron el patrón.

Otra posibilidad, si no queremos todas las coincidencias, es utilizar finditer, que devuelve un iterador con el que consultar uno a uno los distintos MatchObject.

Las expresiones regulares no solo permiten realizar búsquedas o comprobaciones, sino que, como comentamos anteriormente, también tenemos funciones disponibles para dividir la cadena o realizar reemplazos.

La función split sin ir más lejos toma como parámetros un patrón, una cadena y un entero opcional indicando el número máximo de elementos en los que queremos dividir la cadena, y utiliza el patrón a modo de puntos de separación para la cadena, devolviendo una lista con las subcadenas.

La función sub toma como parámetros un patrón a sustituir, una cadena que usar como reemplazo cada vez que encontremos el patrón, la cadena sobre la que realizar las sustituciones, y un entero opcional indicando el número máximo de sustituciones que queremos realizar.

Al llamar a estos métodos lo que ocurre en realidad es que se crea un nuevo objeto de tipo RegexObject que representa la expresión regular, y se llama a métodos de este objeto que tienen los mismos nombres que las funciones del módulo.

Si vamos a utilizar un mismo patrón varias veces nos puede interesar crear un objeto de este tipo y llamar a sus métodos nosotros mismos; de esta forma evitamos que el intérprete tenga que crear un nuevo objeto cada vez que usemos el patrón y mejoraremos el rendimiento de la aplicación.

Para crear un objeto RegexObject se utiliza la función compile del módulo, al que se le pasa como parámetro la cadena que representa el patrón que queremos utilizar para nuestra expresión regular y, opcionalmente, una serie de flags de entre los que comentamos anteriormente.

Comentarios
  1. Excelnte artículo, muy interesante, unico y ayuda mucho.

    Responder

  2. JRGCUBAN0

    Muy bueno la verdad, buena explicación, con buenos ejemplos, etc.

    Responder

  3. En la parte que dice:

    __________________________

    # \s

    Cualquier carácter en blanco. Equivale a [ \t\n\r\f\v]
    # \s

    Cualquier carácter que no sea un espacio en blanco. Equivale a [^ \t\n\r\f\v]

    ________________________

    Creo que está mal. Debería ser algo como esto:

    __________________________

    # \s

    Cualquier carácter en blanco. Equivale a [ \t\n\r\f\v]
    # \S

    Cualquier carácter que no sea un espacio en blanco. Equivale a [^ \t\n\r\f\v]

    ________________________

    Un saludo!

    Responder

  4. Pues si, debería ser una S mayúscula. Cambiado.
    Gracias.

    Responder

  5. yobis

    esta bueno el articulo

    Responder

  6. Hola:

    Muy buen artículo. Pero hay algun fallo sintáctico en algunos ejemplos, en los que pones el caracter de escape pero no el . 🙂 Por ejemplo

    mo = re.match(“http://.+\net”, “http://mundogeek.net”)

    debería ser

    mo = re.match(“http://.+\.net”, “http://mundogeek.net”)

    Del mismo modo con

    mo = re.match(“http://(.+)\net”, “http://mundogeek.net”)

    Pasaría a ser

    mo = re.match(“http://(.+)\.net”, “http://mundogeek.net”)

    Y

    mo = re.match(“http://(.+)\(.{3})”, “http://mundogeek.net”)

    Se convierte en

    mo = re.match(“http://(.+)\.(.{3})”, “http://mundogeek.net”)

    Saludos

    Responder

  7. Muy bueno el tutorial, me aclaraste muchas cosas de una forma simple. Gracias!

    Responder

  8. Muchas gracias por compartir esta explicación. Muy útil y pedagógica. Felicitaciones

    Responder

  9. Javier

    Muchas gracias por el tutorial.

    Tienes razón r3D, estaba haciendo algunas pruebas y también me he dado cuenta de que en los ejemplos después del carácter de escape falta el punto correspondiente.

    Responder

  10. David

    Buenas, estoy un poco liado con esto de las expresiones regulares, yo lo que quiero es que dado:

    loquesealoquesealoquesea2

    Me devuelva:
    group(0) -> loquesea
    group(1) -> loquesea2

    He probado con:
    mo = re.match(“(.)(.+)“,”012“)
    Y me devuelve:
    >>> mo.groups()
    (‘0’, ‘12′)
    1
    2

    ¿Alguna sugerencia?

    Responder

  11. David

    lo siento pero el comentario anterior no se ve como yo quiero, ya que está interpretando el código xml que había puesto.

    Saludos.

    Responder

  12. David

    Es decir, si yo pongo:

    000a1ba2b

    Me devuelva:
    group(0) -> 1111
    group(1) -> 2222

    He probado con:
    mo = re.match(“(.)a(.+)b”,”0a1ba2b”)
    Y me devuelve:
    >>> mo.groups()
    (‘0’, ‘1ba2’)

    ¿Alguna sugerencia?

    Responder

    • Hola David, no creo que las Exp.Reg. sirvan para tu caso; ¿Cuál sería la utilidad, o finalidad del resultado que propones?

      Responder

      • import re
        def main():
        l = []
        i = 0
        while i < 1000000:
        if re.match("[0-9]*9[0-9]*", str(i)):
        l.append(i)
        i += 1
        print l[150]
        raw_input()

        if __name__ == '__main__':
        main()

        Realizen ese ejercicio en vb.net 2008.
        Dios los bendiga.

        Responder

    • Gustavo

      str=’000a1ba2b’
      lst=list(re.search(‘([1-9]).*([1-9])’,str).groups())
      c=0
      for g in lst:
      lst[c]=g*4
      c+=1

      No use los modulos de re, pero esto te saca los dos primeros numeros en el string que sean del 1 al 9, 4 veces

      Responder

  13. Excelente tutorial!, ahora me quedo mucho mas claro el uso del modulo re. Gracias y hasta pronto!

    Responder

  14. Bernardo

    Muy bueno! Claro y sencillo paso a paso!!!!
    Saludos!

    Responder

  15. Me ha gustado, y me ha ayudado.

    Muchas gracias!!

    Responder

  16. picea

    Excelente tutorial.

    Muy bien explicado, resumido y con ejemplos claros.
    Enhorabuena y a seguir asi que muchos lo agradecemos enormemente!

    Responder

  17. Emanuel

    Hola!!!
    Muy buena la pagina…
    Mi duda es si yo le asigno a la variable “a” la frase “mi mama me mima” y le aplico esta función id(a) me devuelve este valor
    >>> id(a)
    38025856

    ¿Qué significa id()?

    Muchas Gracias

    Responder

  18. Jesus

    gracias de verdad!

    Responder

  19. Marcela

    Esta muy bueno el articulo, tengo una duda quisiera saber si me quieren responder, tengo una cadena y la estoy convirtiendo en una lista de palabras, los signos de puntuación los elimino con .strip(),sin embargo, no he podido eliminar las comillas. ¿Cómo hago?
    Muchas gracias si me pueden responder

    Responder

    • Gustavo

      Mira podrias poner .strip(“‘”) para las comillas simples y .strip(‘”‘) para las comillas dobles

      Responder

  20. Zuxana

    Hola, quería ver si me podían ayudar con una duda.
    Necesito que la cadena que va a buscar esté encerrada entre paréntesis, pero aquí los paréntesis son carácteres especiales. ¿Cómo le podría hacer? :S

    Responder

    • Gustavo

      escapa a los parentesis con el caracter de escapado \( osea, en vez de poner ( pones \(

      Responder

  21. Mauro

    Man, una genialidad el tuto… me enseñaste un resto… tengo una pregunta… como escapo el caracter “\” es decir… como hago para que me lo encuentre…

    Responder

  22. Pablo3D

    Es conveniente definir los patrones en formato raw, para no interpretar los caracteres especiales escapados:
    r”texto”
    Por ejemplo la cadena “\n” contiene un unico carácter, mientras que la cadena r”\n” contiene dos caracteres.

    Ver: http://docs.python.org/2/library/re.html

    Responder

  23. manuel

    Una ayuda por favor como puede eliminar en una cadena los paracentesis, la coma, los puntos y las comillas

    Responder

  24. fabian

    Gracias lo necesitaba para un trabajo que bueno
    que gran lenguaje es python

    Responder

  25. maiself

    la re le falta el punto antes del “net”

    mo = re.match(“http://.+\.net”, “http://mundogeek.net”)
    print mo.group()

    Responder

Deja un comentario