expresiones regulares en python

22
Basilio Carrero Nevado | Teoría de Autómatas y Computación | 16 de abril de 2014 Abstract: This document is an introductory tutorial to using regular expressions in Python with the re module based on A.M. Kuchling’s Regular Expression HOWTO. It provides a gentler introduction than the corresponding section in the Library Reference.

Upload: basilio-carrero-nevado

Post on 19-Jan-2016

278 views

Category:

Documents


0 download

DESCRIPTION

Abstract: This document is an introductory tutorial to using regular expressions in Python with the re module based on A.M. Kuchling’s Regular Expression HOWTO. It provides a gentler introduction than the corresponding section in the Library Reference.Autor: Basilio Carrero Nevado

TRANSCRIPT

Page 1: Expresiones regulares en Python

Basilio Carrero Nevado | Teoría de Autómatas y Computación | 16 de abril

de 2014

Abstract: This document is an introductory tutorial to using regular

expressions in Python with the re module based on A.M. Kuchling’s Regular

Expression HOWTO. It provides a gentler introduction than the corresponding

section in the Library Reference.

Page 2: Expresiones regulares en Python

PÁGINA 1

CONTENIDO

Introducción ...................................................................................................... 2

Lo Básico .......................................................................................................... 2

Caracteres Coincidentes ...................................................................................... 2

Notación de cadenas en crudo ............................................................................... 3

Repeticiones..................................................................................................... 4

Usando expresiones regulares ............................................................................... 5

Compilación de expresiones regulares ..................................................................... 5

Encajando Patrones ............................................................................................ 6

Funciones del módulo ......................................................................................... 9

Funciones de cadenas ......................................................................................... 9

Patrones Avanzados ........................................................................................... 10

Más Meta-caracteres ......................................................................................... 10

Agrupamiento ................................................................................................. 12

Grupos etiquetados y grupos no-capturados ............................................................ 14

Aserciones de búsqueda hacia delante ................................................................... 16

Modificando cadenas .......................................................................................... 17

Partiendo cadenas............................................................................................ 18

Búsqueda y reemplazo....................................................................................... 19

Recursos y herramientas de desarrollo y depuración.................................................. 21

Online .......................................................................................................... 21

Off-line ......................................................................................................... 21

Referencias ...................................................................................................... 21

Page 3: Expresiones regulares en Python

PÁGINA 2

INTRODUCCIÓN

El módulo re provee de soporte para el uso de expresiones regulares al estilo Perl desde la versión

1.5, versiones anteriores de Python utilizan el módulo regex que proporciona patrones al estilo

Emacs. Este último fue eliminado completamente a partir de la versión 2.5.

Las expresiones regulares constituyen un pequeño lenguaje de propósito específico empotrado en

Python y disponible a través del módulo re. Usando dicho lenguaje podemos especificar reglas

que encajarán contra un posible conjunto de cadenas de texto. Se pueden usar para comprobar si

una cadena sigue un patrón determinado o si existe coincidencia en algún punto de la misma,

también es posible trocearlas o sustituir ocurrencias que encajen en el patrón.

Los patrones son compilados a una serie de bytecodes que son ejecutados por el motor de

emparejamiento escrito en C a fin de verificar las posibles coincidencias.

El lenguaje de las expresiones regulares es relativamente pequeño y restringido, por lo que no

todas las tareas pueden realizarse de esta forma, incluso en ocasiones pueden añadir complejidad

innecesaria al problema. En estos casos es preferible usar código Python que resulte en una mayor

claridad en detrimento de la velocidad de ejecución.

LO BÁSICO

CARACTERES COINCIDENTES

La mayoría de los caracteres coinciden por sí mismos con su literal correspondiente. Por ejemplo:

“texto” coincide con la cadena “texto”.

Por otro lado tenemos los Meta-caracteres, que son caracteres especiales que afectan a la RE

alterando su significado. Esta es la lista completa que veremos a lo largo del documento:

. ^ $ * + ? { } [ ] \ | ( )

La pareja de corchetes nos permite especificar la clase a la que pertenece un carácter, esto es el

conjunto de caracteres en el que puede darse la coincidencia. Por ejemplo [abc] coincide con

los caracteres ‘a’, ‘b’ o ‘c’. También es posible emplear rangos, separando el carácter inicial

del final mediante ‘-‘, quedando [a-c].

Los Meta-caracteres no están activos dentro de una clase, son interpretados como literales,

perdiendo su significado especial. [akm$] coincide con ‘a’, ‘k’, ‘m’, o ‘$’.

También es posible coincidir con los caracteres que no se listan complementando el conjunto,

esto se indica incluyendo ‘^’ como primer carácter de la clase. Por ejemplo [^5] coincide con

cualquier carácter excepto ‘5’.

Page 4: Expresiones regulares en Python

PÁGINA 3

El Meta-caracteres más importante es la barra invertida: ‘\’. Al igual que en las cadenas en

Python, la barra invertida puede ir seguida de otros caracteres para expresar secuencias con un

significado especial. Además, es usado para escapar cualquier otro Meta-carácter cuando no

queremos que corresponda con ningún patrón. Por ejemplo, si queremos coincidir con ‘[‘ o ‘\’,

debemos precederlos con la barra invertida: ‘\[’ o ‘\\’.

Algunas de las secuencias que comienzan con ‘\’ son conjuntos predefinidos de caracteres que se

emplean habitualmente como el conjunto de los números, el conjunto de las letras o cualquier

cosa que no sea un carácter blanco. A continuación se presentan en la siguiente tabla algunas de

las secuencias más comunes junto a sus clases equivalentes. Para obtener una lista completa de

secuencias y sus definiciones de clase para cadenas Unicode, vea la última parte de la sección

“Regular Expression Syntax” en la documentación oficial de Python.

Secuencia Descripción Clase equivalente

\d Cualquier dígito decimal [0-9]

\D Cualquier carácter no numérico [^0-9]

\s Cualquier carácter blanco [ \t\n\r\f\v]

\S Cualquier carácter no blanco [^ \t\n\r\f\v]

\w Cualquier carácter alfanumérico [a-zA-Z0-9_]

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

Las secuencias pueden incluirse en la definición de una clase. Por ejemplo [\s,.] concuerda con

cualquier carácter blanco, ‘,’ o ‘.’

Finalmente en esta sección, el Meta-carácter ‘.’ encaja con cualquier carácter excepto el de

nueva línea (incluso es posible hacerlo coincidir también con este mediante la bandera de

compilación re.DOTALL como veremos más adelante).

NOTACIÓN DE CADENAS EN CRUDO

Como señalamos anteriormente, para escapar un Meta-carácter en una RE hemos de anteponer la

barra invertida, no obstante esto entra en conflicto con la representación en Python de las cadenas

de literales, desde que se utiliza el mismo carácter para la misma función.

Pongamos por ejemplo que estamos tratando un fichero LaTeX y deseamos escribir una RE que

encaje con la cadena “\section”. La RE que deberíamos compilar sería \\section, a su vez,

para representar dicha RE como una cadena de literales tenemos que volver a escapar cada barra

invertida, resultando en “\\\\section”.

Page 5: Expresiones regulares en Python

PÁGINA 4

Esto puede complicar la lectura y dificultar la compresión de nuestras RE. La solución pasa por

usar la notación en crudo de cadenas, que es un modo de representar cadenas anteponiendo el

prefijo ‘r’, en el que la barra invertida no tiene significado especial y por tanto no necesitamos

escaparla por segunda vez. La siguiente tabla ilustra la diferencia entre la notación regular y la

notación en crudo:

Notación Regular Notación en Crudo

"ab*" r"ab*"

"\\\\section" r"\\section"

"\\w+\\s+\\1" r"\w+\s+\1"

REPETICIONES

A continuación veremos cómo establecer que una porción de la RE se repita un cierto número de

veces.

El primer Meta-carácter que nos brinda esta capacidad es ‘*’, que especifica que el carácter

precedente puede aparecer cero o más veces en lugar de una sola vez. Por ejemplo: ga*to

coincidirá con gto (0 caracteres a), gato (1 a), gaaato (3 a)….

El motor interno presenta algunas limitaciones derivadas del tamaño de un entero en C que

impiden coincidir más de dos mil millones de veces, pero probablemente no tendrás memoria

suficiente para representar una cadena tan larga, por lo que no deberías preocuparte por este

límite.

Otro Meta-carácter repetidor es ‘+’, que requiere que el carácter precedente aparezca al menos

una vez.

También está ‘?’, que especifica que el carácter puede aparecer una o ninguna veces, se puede

entender como opcional el carácter al que se aplica.

Por último tenemos el más complejo ‘{m,n}’, dónde m y n son enteros positivos, m es mínimo y

n el máximo número de ocurrencias del carácter que cuantifica. Es posible omitir uno de los dos,

en caso de omitir m se toma como mínimo 0, por el contrario si es n el que se omite se toma como

máximo infinito.

Nótese que ‘{0,}’ es lo mismo que ‘*’, ‘{1,}’ es equivalente a ‘+’ y ‘{0,1}’ es igual a ‘?’, no

obstante se prefiere el uso de los segundos debido a que son más cortos y fáciles de leer.

Cabe destacar que las repeticiones anteriores son voraces, esto significa que el motor de

emparejamiento intentará consumir tantas repeticiones como le sea posible antes de pasar a la

siguiente porción de la RE, si la porción de la RE siguiente al carácter repetido no coincide, volverá

hacia atrás y lo comprobará de nuevo con menos repeticiones.

Page 6: Expresiones regulares en Python

PÁGINA 5

Se debe tener esto en cuenta cuando queremos construir expresiones que encajen en una cadena

que presente delimitadores balanceados, como por ejemplo los corchetes angulares que encierran

una etiqueta HTML.

Supongamos que tenemos la siguiente cadena: “<html><head><title>Title</title>”. Si

queremos capturar una sola etiqueta la RE <.*> no nos serviría, ya que ‘<’ coincidiría; pero ‘.*’ consumiría el resto de la cadena y al volver hacia atrás la coincidencia se daría con el último

corchete de la cadena.

Es en este tipo de situaciones dónde se hace necesario el uso de los cualificadores no voraces:

‘*?’, ‘+?’, ‘??’ y ‘{m,n}?’ que encajan en la menor cantidad de texto posible. En el caso del

ejemplo anterior deberíamos usar <.*?> para que el motor pruebe con el segundo corchete justo

después de haber coincidido el primero.

USANDO EXPRESIONES REGULARES

Ahora que conocemos las nociones básicas podemos comenzar a utilizar Expresiones Regulares en

Python.

COMPILACIÓN DE EXPRESIONES REGULARES

Es el módulo re el que nos proporciona una interfaz con el motor de expresiones regulares

permitiéndonos compilar las RE en objetos y posteriormente realizar las comparaciones con ellos.

Estos objetos nos proporcionan diferentes métodos para realizar operaciones como búsquedas de

coincidencias o la sustitución de cadenas:

>>> import re

>>> p = re.compile('ab*')

>>> p

<_sre.SRE_Pattern object at 0x...>

Las RE se pasan a re.compile() como una cadena de texto, esto se debe a que las RE no forman

parte del núcleo de Python y no tienen una sintaxis propia sino que re es una extensión para el

módulo en C incluido en Python al igual que sockets o zlib.

Esto simplifica el lenguaje pero complica el escapado de caracteres especiales, que como

mencionamos anteriormente, puede solventarse mediante la notación en crudo de cadenas.

re.compile() admite banderas de compilación que nos permiten introducir variaciones en la

sintaxis. Las banderas del módulo re tienen un nombre largo y uno corto que podemos usar

indistintamente (El nombre corto es el mismo que los modificadores para patrones en Perl). Es

posible especificar varias banderas mediante el operador de bits OR:

>>> p = re.compile('ab*', re.I | re.M)

Page 7: Expresiones regulares en Python

PÁGINA 6

Esta tabla ilustra las diferentes banderas y el efecto que producen, para una descripción detallada

revise la sección 7.2.2 de la documentación oficial “Module Contents”:

Bandera Efecto

DOTALL, S Hace coincidir ‘.’ Con cualquier carácter, incluida la nueva línea.

IGNORECASE, I Caso Insensitivo.

LOCALE, L Tiene en cuenta la “locale” actual.

MULTILINE, M Coincidencia Multi-línea (afecta a ‘^’ y ‘$’).

VERBOSE, X Activa el modo detallado, que permite escribir las RE de una forma más legible.

UNICODE, U Hace algunos caracteres especiales como \w, \b, \s y \d dependientes de la base de datos de caracteres Unicode.

ENCAJANDO PATRONES

Una vez que tenemos el patrón compilado podemos comenzar a emplear sus métodos y atributos.

Aquí veremos los más importantes, para una lista completa consulte la documentación oficial del

módulo re.

Método/Atributo Propósito

match() Determina si la RE encaja desde el principio de la cadena.

search() Escanea la cadena buscando la coincidencia en cualquier lugar.

findall() Encuentra todas las sub-cadenas donde encaja la RE y las retorna como una lista.

finditer() Encuentra todas las sub-cadenas donde encaja la RE y las retorna como un iterador.

match() y search() retornan None si no existe coincidencia alguna. Si tienen éxito retornan un

objeto coincidencia, que contiene información sobre la misma: Inicio, fin, sub-cadena encajada y

mucho más.

Si tienes Tkinter instalado tal vez te interese echar un vistazo a Tools/scripts/redemo.py, un

programa de demostración incluido en la propia distribución de Python que te permite introducir

REs y cadenas, y mostrar cuando la RE encaja o falla. Este script puede ser útil cuando intentamos

depurar una RE complicada.

Otra herramienta interactiva interesante para el desarrollo y testeo de REs es Kodos, creada por

Phil Schwartz.

También existen herramientas on-line como Pythex de Gabriel Rodríguez o Regex101, que es capaz

de explicar REs en diferentes lenguajes, ambas inspiradas en Rubular.

Page 8: Expresiones regulares en Python

PÁGINA 7

Usaremos el intérprete estándar de Python para los siguientes ejemplos. Para comenzar cargue el

intérprete importe el módulo re y compile una RE:

Python 2.2.2 (#1, Feb 10 2003, 12:57:01)

>>> import re

>>> p = re.compile('[a-z]+')

>>> p #doctest: +ELLIPSIS

<_sre.SRE_Pattern object at 0x...>

Ya podemos encajar diferentes cadenas contra la RE [a-z]+. Una cadena vacía no encajará debido

a que ‘+’ significa al menos una repetición. Usaremos print() para visualizar esto en el intérprete

ya que de lo contrario no obtendríamos salida alguna:

>>> p.match("")

>>> print p.match("")

None

Ahora probaremos con “tempo” que debería encajar en la RE. En este caso match() retornará

un objeto coincidencia que almacenaremos en una variable para su posterior uso:

>>> m = p.match('tempo')

>>> m

<_sre.SRE_Match object at 0x...>

Ahora podemos preguntar al objeto coincidencia sobre la cadena encajada. Para ello, estos

objetos disponen de algunos métodos y atributos, los más importantes son:

Método/Atributo Propósito

group() Retorna la sub-cadena encajada.

start() Retorna la posición inicial en la que se produjo la coincidencia.

end() Retorna la posición en la que termina la coincidencia.

span() Retorna una tupla (inicio, fin) con ambas posiciones.

Probaremos cada uno con el ejemplo anterior para ver los detalles:

>>> m.group()

'tempo'

>>> m.start(), m.end()

(0, 5)

>>> m.span()

(0, 5)

Page 9: Expresiones regulares en Python

PÁGINA 8

Puesto que match() solo comprueba si la RE encaja desde el principio de la cadena, la posición

inicial de la cadena será siempre 0, sin embargo search() escaneará la cadena entera:

>>> print p.match('::: message')

None

>>> m = p.search('::: message'); print m

<_sre.SRE_Match object at 0x...>

>>> m.group()

'message'

>>> m.span()

(4, 11)

En los programas actuales es común almacenar el objeto coincidencia en una variable y comprobar

si fue None de la siguiente forma:

p = re.compile( ... )

m = p.match( 'string goes here' )

if m:

print 'Match found: ', m.group()

else:

print 'No match'

En ocasiones puedes estar tentado de usar re.match() y simplemente añadir ‘.*’ al comienzo

de la RE. No lo hagas y utiliza re.search() en su lugar. El compilador de REs las analiza para

acelerar el proceso de búsqueda, por ejemplo, un patrón que contenga “Cuervo” debe encajar

en una cadena que comience por ‘C’. El análisis permite al motor escanear rápidamente la cadena

buscando el carácter inicial y probando el resto solo si lo encuentra. Al añadir ‘.*’ fuerzas al

motor de emparejamiento a escanear la cadena hasta el final y volver hacia atrás para encontrar

la coincidencia con el resto de la RE.

findall() retorna una lista de cadenas coincidentes, este método necesita crear la lista completa

antes de poder retornarla como resultado:

>>> p = re.compile('\d+')

>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-

leaping')

['12', '11', '10']

Page 10: Expresiones regulares en Python

PÁGINA 9

Por ultimo finditer() retorna una secuencia de instancias de objeto coincidencia como un

iterador:

>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')

>>> iterator

<callable-iterator object at 0x...>

>>> for match in iterator:

... print match.span()

...

(0, 2)

(22, 24)

(29, 31)

FUNCIONES DEL MÓDULO

No es estrictamente necesario compilar las RE antes de utilizarlas, re proporciona funciones de

alto nivel llamadas match(), search(), findall(), sub() entre otras. Estas funciones toman

los mismos argumentos que sus homologas del objeto patrón con la RE en forma de cadena como

primer argumento y retornan lo mismo: None o una instancia de objeto coincidencia.

>>> print re.match(r'From\s+', 'Fromage amk')

None

>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')

<_sre.SRE_Match object at 0x...>

Realmente, lo que hacen estas funciones es crear el objeto RE por ti y llaman sobre él al método

correspondiente. También almacenan dicho objeto en una caché para que las subsiguientes

llamadas sean más rápidas.

Utilizar estas funciones o no, depende de la cantidad de veces que se use la RE y del estilo de

codificación de cada uno. Si el programa utiliza muchas RE o si reusa las mismas en diferentes

partes puede ser útil agrupar las definiciones en un mismo lugar y compilarlas a la vez, por otro

lado, si la RE se utiliza puntualmente estas funciones pueden resultar más convenientes.

FUNCIONES DE CADENAS

Establecer coincidencias en diferentes conjuntos de caracteres junto con las repeticiones son las

principales capacidades distintivas de las RE. Sin embargo, no siempre es adecuado usar

expresiones regulares. Si queremos comprobar coincidencias contra una cadena fija o un solo

carácter, no estaremos usando ninguna de las características de las RE.

El tipo cadena dispone de algunos métodos para realizar operaciones con cadenas fijas que suelen

ser bastante más rápidos debido a que se implementan con un pequeño bucle en lugar de usar un

motor completo.

Page 11: Expresiones regulares en Python

PÁGINA 10

Por ejemplo, si queremos reemplazar la ocurrencia “cara” por “cruz” considere utilizar

replace() en lugar de re.sub(). Nótese que replace() también sustituye la ocurrencia dentro

de las propias palabras, transformando “caracol” en “cruzcol”, para evitar esto

necesitaríamos usar el patrón \bcara\b ya que este tipo de operaciones sobrepasan las

capacidades de replace().

Otra tarea común es eliminar la ocurrencia de un solo carácter o reemplazarlo por otro,

translate() puede hacer ambas cosas y será siempre más rápido que cualquier expresión regular.

En síntesis, si los métodos de cadenas son suficiente, son preferibles a las expresiones regulares.

PATRONES AVANZADOS

MÁS META-CARACTERES

Hay Meta-caracteres que no hemos visto aún, cubriremos la mayoría en esta sección.

Los Meta-caracteres restantes caracteres son afirmaciones de ancho cero. No provocan que el

motor consuma caracteres de la cadena, simplemente tienen éxito o fallan. Por ejemplo, ‘\b’ es

una afirmación que indica que la posición actual se sitúa en los límites de una palabra, pero no

cambia dicha posición, por ende este tipo de Meta-caracteres no debe llevar modificadores de

repetición puesto que si tienen éxito una vez, también lo tendrán un número infinito de veces.

|

Es el operador lógico OR. Si A y B son expresiones regulares, A|B coincidirá con cualquier

cadena que encaje en A o en B. Tiene muy baja precedencia para que tenga un

comportamiento razonable a la hora de alternar cadenas con varios caracteres.

“Crow|Servo” encajará tanto con “Crow” como con “Servo”, y no en “Cro” o ‘w’

ni en ‘S’ o “ervo”.

Para encajar contra el literal ‘|’, use ‘\|’ o enciérrelo en los corchetes de clase: [|].

^

A menos que se especifique la bandera MULTILINE, encajará solamente al inicio de la

cadena. En modo Multi-línea encaja inmediatamente después de cada nueva línea:

>>> print re.search('^From', 'From Here to Eternity')

<_sre.SRE_Match object at 0x...>

>>> print re.search('^From', 'Reciting From Memory')

None

Page 12: Expresiones regulares en Python

PÁGINA 11

$

Encaja al final de la línea, la cual se define como el final de la cadena o la posición

siguiente al carácter de nueva línea.

>>> print re.search('}$', '{block}')

<_sre.SRE_Match object at 0x...>

>>> print re.search('}$', '{block} ')

None

>>> print re.search('}$', '{block}\n')

<_sre.SRE_Match object at 0x...>

Para encajar contra el literal ‘$’, use ‘\$’ o enciérrelo en los corchetes de clase: [$].

\A

Encaja exclusivamente al inicio de la cadena. Si no se especifica la bandera MULTILINE

‘\A’ y ‘^’ son equivalentes.

\Z

Encaja exclusivamente al final de la cadena.

\b

Límite de palabra. Encaja solo al principio o al final de una palabra, la cual se define como

una secuencia de caracteres alfanuméricos separados por un carácter blanco o uno no-

alfanumérico.

El siguiente ejemplo coincide si “class” constituye una palabra por sí misma y falla en

caso contrario:

>>> p = re.compile(r'\bclass\b')

>>> print p.search('no class at all')

<_sre.SRE_Match object at 0x...>

>>> print p.search('the declassified algorithm')

None

>>> print p.search('one subclass is')

None

Page 13: Expresiones regulares en Python

PÁGINA 12

Hay dos detalles que debemos tener presentes: El primero es que esta secuencia es el

peor caso de colisión entre la representación de cadenas de literales en Python y las

secuencias de expresiones regulares debido a que ‘\b’ es el carácter de retroceso (valor

8 en ASCII). Si no utilizas notación en crudo tu expresión regular no tendrá el

comportamiento esperado. El siguiente ejemplo es igual al anterior, salvo que omite el

prefijo ‘r’:

>>> p = re.compile('\bclass\b')

>>> print p.search('no class at all')

None

>>> print p.search('\b' + 'class' + '\b')

<_sre.SRE_Match object at 0x...>

El segundo es que no tiene caso dentro de la definición de la clase de un carácter, ‘\b’

representa el carácter de retroceso por razones de compatibilidad.

\B

Es el opuesto de ‘\b’, encaja cuando la posición actual no está situada en el límite de

una palabra.

AGRUPAMIENTO

Frecuentemente necesitamos obtener más información a parte de si la RE encaja o no en una

cadena. Las expresiones regulares se usan a menudo para diseccionar cadenas subdividiendo la RE

en grupos que encajan en los distintos componentes de interés. Por ejemplo, las líneas de la

cabecera de una RFC-822 se compone de un nombre y un valor separados por ‘:’ de la siguiente

forma:

From: [email protected]

User-Agent: Thunderbird 1.5.0.9 (X11/20061227)

MIME-Version: 1.0

To: [email protected]

Podemos conseguir esto escribiendo una RE que encaje en cada línea y tenga dos grupos, uno para

el nombre y otro para el valor.

Los grupos se encuadran entre los Meta-caracteres ‘(‘, ‘)’ y agrupan las expresiones que

contienen. Se pueden repetir los contenidos de un grupo mediante los cuantificadores ‘*’, ’+’,

‘?’ o ‘{m, n}’. Por ejemplo (ab)+ encaja con una o más repeticiones del grupo ab:

>>> p = re.compile('(ab)*')

>>> print p.match('ababababab').span()

(0, 10)

Page 14: Expresiones regulares en Python

PÁGINA 13

Los grupos van numerados de izquierda a derecha comenzando desde 0. El grupo 0 siempre está

presente y corresponde a la RE entera, además cada grupo captura el índice inicial y final del

texto donde encaja, para recuperarlos se puede pasar como argumento el número del grupo a los

métodos group(), start(), end(), and span():

>>> p = re.compile('(a)b')

>>> m = p.match('ab')

>>> m.group()

'ab'

>>> m.group(0)

'ab'

Los grupos pueden estar anidados, para determinar el número basta con contar los paréntesis

abiertos de izquierda a derecha:

>>> p = re.compile('(a(b)c)d')

>>> m = p.match('abcd')

>>> m.group(0)

'abcd'

>>> m.group(1)

'abc'

>>> m.group(2)

'b'

A group() se le pueden pasar varios grupos a la vez, en tal caso retornará una tupla conteniendo

los valores correspondientes a tales grupos:

>>> m.group(2,1,2)

('b', 'abc', 'b')

Si no se especifica ningún argumento la tupla contendrá las cadenas de todos los grupos existentes:

>>> m.groups()

('abc', 'b')

Las referencias hacia atrás nos permiten especificar que el contenido de un grupo previamente

capturado debe encontrarse en la posición actual de la cadena. Por ejemplo ‘\1’ tendrá éxito si

el contenido del grupo 1 se encuentra en la posición actual y falla en caso contrario.

Recuerda que en Python se utiliza la barra invertida seguida de números para denotar caracteres

arbitrarios en las cadenas de literales, por tanto asegúrate de usar la notación en crudo cuando

introduzcas referencias hacia atrás en una RE.

Page 15: Expresiones regulares en Python

PÁGINA 14

Por ejemplo, la siguiente RE detecta palabras repetidas en una cadena:

>>> p = re.compile(r'(\b\w+)\s+\1')

>>> p.search('Paris in the the spring').group()

'the the'

Las referencias hacia atrás como esta, apenas se usan en búsquedas, pero son de gran utilidad a

la hora de realizar sustituciones de texto.

GRUPOS ETIQUETADOS Y GRUPOS NO-CAPTURADOS

Tenemos dos características que nos ayudan a lidiar con este problema, ambas utilizan la misma

sintaxis para la extensión de expresiones regulares, por lo que examinaremos esto primero:

Perl 5 añadió algunas características a las expresiones regulares standard y el módulo re de Python

soporta la mayoría. Hubiera sido difícil elegir un nuevo Meta-carácter o secuencia especial

comenzando con ‘\’ para representar estas nuevas características sin hacerlo más confuso o variar

el standard de Perl.

La solución elegida por los desarrolladores fue usar “(¿...)” como extensión de la sintaxis. ‘?’ inmediatamente después de paréntesis provocaría un error debido a que no habría nada que

repetir y por tanto mantendría la compatibilidad. Los caracteres siguientes a ‘?’ indican el tipo

de extensión utilizada, de esta forma tenemos que “(?=foo)” es una cosa (Una afirmación de

búsqueda hacia delante positiva) y “(?:foo)” otra distinta (Un grupo no-capturado que contiene

la sub-expresión “foo”).

Python a su vez añade una extensión sintáctica a la extensión sintáctica de Perl. Si el siguiente

carácter a ‘?’ es ‘P’, estamos ante una extensión propia de Python. Actualmente estas

extensiones son dos: “(?P<nombre>...)” define un grupo etiquetado y “(?P=nombre)” es una

referencia hacia atrás a un grupo etiquetado. Si en un futuro la sintaxis de Perl variase, el módulo

re se cambiaría para soportar la nueva sintaxis a la vez que se preservan las extensiones

específicas de Python por compatibilidad.

Ahora que conocemos la sintaxis general para extensiones podemos centrarnos en las

características para trabajar con grupos en REs complejas:

Las REs más elaboradas pueden llegar a utilizar muchos grupos tanto para capturar sub-cadenas

de interés como para estructurar la RE en sí misma. En REs complejas se hace difícil seguir la pista

de los números de grupo y se hace molesto introducir nuevos grupos, especialmente al comienzo

de la RE, debido a que la numeración de los grupos subsiguientes cambiaría.

Podemos solventar esto con grupos etiquetados: en lugar de referenciar un grupo con un número,

los referenciamos con un nombre.

Page 16: Expresiones regulares en Python

PÁGINA 15

La sintaxis para los grupos etiquetados es específica de Python: “(?P<nombre>...)”, donde

“nombre” es el nombre del grupo. Los grupos etiquetados tienen exactamente el mismo

comportamiento que los grupos, con el añadido de estar asociados a un nombre. Los métodos de

los objetos coincidencia pueden lidiar con ambas referencias:

>>> p = re.compile(r'(?P<word>\b\w+\b)')

>>> m = p.search( '(((( Lots of punctuation )))' )

>>> m.group('word')

'Lots'

>>> m.group(1)

'Lots'

La sintaxis de las referencias hacia atrás del tipo “(…)\1” refieren a grupos mediante su número.

Existe una variante que permite hacerlo mediante el nombre, es otra de las extensiones

específicas de Python: “(?:nombre)” indica que el contenido del grupo nombrado debe estar

presente de nuevo en el punto actual. La RE para encontrar palabras repetidas (\b\w+)\s+\1

puede ser reescrita como: (?P<word>\b\w+)\s+(?P=word)

>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')

>>> p.search('Paris in the the spring').group()

'the the'

A veces ocurre que queremos recoger parte de una expresión regular pero no estamos interesados

en recuperar los contenidos del grupo, esto se puede explicitar utilizando grupos no-capturados:

“(?:...)” donde podemos reemplazar “...” con cualquier otra expresión regular:

>>> m = re.match("([abc])+", "abc")

>>> m.groups()

('c',)

>>> m = re.match("(?:[abc])+", "abc")

>>> m.groups()

()

Excepto por el hecho de que no podemos recuperar los contenidos del grupo coincidente, un grupo

no-capturado tiene el mismo comportamiento que un grupo capturado. Puedes poner cualquier

cosa dentro, emplear repeticiones y anidarlo en otro grupo (capturado o no capturado).

“(?:...)” es particularmente útil al modificar REs existentes ya que no hace variar la numeración

del resto de grupos.

No existen diferencias de rendimiento entre el uso de grupos capturados y no-capturados ni

tampoco es una forma más rápida que la otra.

Page 17: Expresiones regulares en Python

PÁGINA 16

ASERCIONES DE BÚSQUEDA HACIA DELANTE

Otras afirmaciones de ancho cero son las aserciones de búsqueda hacia delante. Pueden

expresarse de forma positiva o negativa:

(?=...)

Aserción positiva de búsqueda hacia delante. Tiene éxito cuando la RE contenida,

representada por “...” encaja en la posición actual y falla en caso contrario. Pero al

probar la expresión contenida en la aserción, el motor de emparejamiento no avanza y

continua probando el resto del patrón justo donde comenzó la aserción.

(?!...)

Aserción negativa de búsqueda hacia delante. Es el opuesto del anterior, Tiene éxito

cuando la RE contenida no encaja y falla en caso contrario.

Para detallar esto veamos un caso concreto en el que las aserciones de búsqueda hacia delante

son de utilidad: Consideremos un patrón simple que encaje en un nombre de fichero compuesto

por un nombre y una extensión separados por ‘.’. Por ejemplo “news.rc” donde “news” es el

nombre y “rc” la extensión:

El patrón es bastante simple: .*[.].*$

Nótese que ‘.’ Necesita un tratamiento especial por ser un Meta-carácter y para hacerlo encajar

con su literal lo hemos encerrado en la definición de clase. También el ‘$’ final asegura que el

resto de la cadena esté incluida en la extensión. Esta RE encaja con “foo.bar”,

“autoexec.bat”, “sendmail.cf” y “printers.conf”.

Ahora lo complicaremos un poquito más: ¿Qué ocurriría si quisiéramos encajar con los nombres de

archivo que no tengan extensión “bat”? Algunos intentos incorrectos serían los siguientes:

.*[.][^b].*$

Este primer intento excluye la extensión “bat” requiriendo que el primer carácter no sea ‘b’.

Esto está mal porque no encajaría en “foo.bar”.

Page 18: Expresiones regulares en Python

PÁGINA 17

.*[.]([^b]..|.[^a].|..[^t])$

La expresión se vuelve más confusa cuando intentamos parchear el primer intento requiriendo

uno de los tres siguientes casos para que se produzca la coincidencia: Que el primer carácter no

sea una ‘b’, que el segundo no sea una ‘a’ o que el tercero no sea una ‘t’. Esto acepta “foo.bar”

y rechaza “autoexec.bat” pero exige que la extensión tenga tres letras. Un nombre de fichero

con una extensión de dos letras como “sendmail.cf” sería rechazado. Complicaremos la

expresión un poco más para intentar solucionarlo:

.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$

En este tercer intento la segunda y tercera letras de la extensión son opcionales para permitir que

encajen nombres de fichero que tengan menos de tres letras en su extensión.

El patrón se ha vuelto bastante complicado, lo que lo hace difícil de leer y comprender. Aún peor,

si el problema cambia y además de “bat” quisiéramos excluir también “exe”, el patrón se

complicaría aún más.

Las aserciones de búsqueda hacia delante vienen a simplificar este tipo de casos. Reformulando

el patrón con una aserción negativa quedaría:

.*[.](?!bat$).*$

La aserción negativa significa: Si la expresión “bat” no encaja en este punto, prueba el resto del

patrón, si bat$ encaja el patrón entero fallará. El ‘$’ del final es necesario para que algo como

“simple.batch” donde la extensión solo comienza con “bat” se permita.

Ahora excluir una extensión adicional es tan simple como añadir un OR en la aserción. El siguiente

patrón excluye tanto “bat” como “exe”:

.*[.](?!bat$|exe$).*$

MODIFICANDO CADENAS

Hasta ahora solo hemos buscado coincidencias en cadenas. Las expresiones regulares también se

usan normalmente para modificar cadenas de distintas formas, usando los siguientes métodos:

Método/Atributo Propósito

split() Parte la cadena en una lista, cortando allá donde la RE coincida.

sub() Encuentra las sub-cadenas donde la RE coincida y las reemplaza por otra diferente.

subn() Igual que sub(), pero retorna la nueva cadena y el número de reemplazos.

Page 19: Expresiones regulares en Python

PÁGINA 18

PARTIENDO CADENAS

El método split() de un patrón es similar al split() de una cadena, pero es mucho más flexible

en cuanto a los delimitadores que se pueden utilizar para realizar el corte, el split() de las

cadenas solo soporta el corte en los caracteres blancos o en sub-cadenas fijas.

.split(cadena[, maxcorte=0])

Podemos limitar el número de cortes fijando un valor para maxcorte. Si maxcorte no es 0, se

realizarán como mucho maxcorte cortes y el resto de la cadena se retornará como último valor

de la lista. En el siguiente ejemplo el delimitador es cualquier secuencia de caracteres no

alfanuméricos:

>>> p = re.compile(r'\W+')

>>> p.split('This is a test, short and sweet, of split().')

['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']

>>> p.split('This is a test, short and sweet, of split().', 3)

['This', 'is', 'a', 'test, short and sweet, of split().']

A veces no solo estamos interesados en conocer el texto entre delimitadores, sino también en el

delimitador que provocó el corte. Si usamos grupos en la RE, sus valores capturados también se

retornarán como parte de la lista. Compara las siguientes llamadas:

>>> p = re.compile(r'\W+')

>>> p2 = re.compile(r'(\W+)')

>>> p.split('This... is a test.')

['This', 'is', 'a', 'test', '']

>>> p2.split('This... is a test.')

['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']

Como mencionamos anteriormente, podemos usar directamente la función del módulo re.split()

en lugar de las del objeto patrón, con la salvedad de que debemos añadir la RE usada como primer

argumento:

>>> re.split('[\W]+', 'Words, words, words.')

['Words', 'words', 'words', '']

>>> re.split('([\W]+)', 'Words, words, words.')

['Words', ', ', 'words', ', ', 'words', '.', '']

>>> re.split('[\W]+', 'Words, words, words.', 1)

['Words', 'words, words.']

Page 20: Expresiones regulares en Python

PÁGINA 19

BÚSQUEDA Y REEMPLAZO

Otra tarea común es reemplazar una cadena por otra. El método sub() toma un valor de

reemplazo, que puede ser otra cadena o una función y la cadena a procesar:

.sub(reemplazo, cadena[, cont=0])

Este es un ejemplo simple en el que se reemplazan los nombres de los colores por la cadena

“colour”:

>>> p = re.compile( '(blue|white|red)')

>>> p.sub( 'colour', 'blue socks and red shoes')

'colour socks and colour shoes'

>>> p.sub( 'colour', 'blue socks and red shoes', count=1)

'colour socks and red shoes'

El método subn() funciona del mismo modo, pero retorna una tupla con la nueva cadena y el

número de reemplazos:

>>> p = re.compile( '(blue|white|red)')

>>> p.subn( 'colour', 'blue socks and red shoes')

('colour socks and colour shoes', 2)

>>> p.subn( 'colour', 'no colours at all')

('no colours at all', 0)

Las coincidencias vacías solo se reemplazan cuando no son adyacentes a la coincidencia anterior:

>>> p = re.compile('x*')

>>> p.sub('-', 'abxd')

'-a-b-d-'

Si el reemplazo es una cadena; cualquier secuencia escapada con una barra invertida se procesa,

es decir, ‘\n’ se convierte en una nueva línea, ‘\r’ en un retorno de carro, etc. Si no se conoce

la secuencia escapada se deja igual.

Las referencias hacia atrás, como ‘\6’, se reemplazan por el grupo correspondiente en la RE. Esto

permite incorporar porciones del texto original en la cadena resultante tras el reemplazo.

El siguiente ejemplo encaja con la palabra “section” seguida de una cadena encerrada entre

corchetes ‘{‘, ‘}’ y cambia “section” por “subsection”:

>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)

>>> p.sub(r'subsection{\1}','section{First} section{second}')

'subsection{First} subsection{second}'

Page 21: Expresiones regulares en Python

PÁGINA 20

También hay una sintaxis para los grupos etiquetados, definidos por “(?P<nombre>...)”.

“\g<nombre>” usará la sub-cadena coincidente con el grupo llamado “nombre” y

“\g<número>” el correspondiente grupo numerado. ‘\g<2>’ es por tanto equivalente a ‘\2’,

pero no es ambiguo en un reemplazo de cadena tal como: \g<2>0 (‘\20’ sería interpretado como

una referencia al grupo 20 en lugar de una referencia al grupo 2 seguida de ‘0’). Las siguientes

sustituciones son equivalentes, pero usan cada una de las tres variantes:

>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)

>>> p.sub(r'subsection{\1}','section{First}')

'subsection{First}'

>>> p.sub(r'subsection{\g<1>}','section{First}')

'subsection{First}'

>>> p.sub(r'subsection{\g<name>}','section{First}')

'subsection{First}'

reemplazo también puede ser una función, lo que nos proporciona incluso más control, en tal

caso la función es llamada en cada ocurrencia no solapante en el patrón. En cada llamada se le

pasa a la función un objeto coincidencia con la información de la coincidencia actual, que puede

utilizar para computar el reemplazo deseado y retornarlo.

En el siguiente ejemplo la función traduce dígitos decimales a hexadecimales:

>>> def hexrepl(match):

... "Return the hex string for a decimal number"

... value = int(match.group())

... return hex(value)

...

>>> p = re.compile(r'\d+')

>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')

'Call 0xffd2 for printing, 0xc000 for user code.'

Page 22: Expresiones regulares en Python

PÁGINA 21

RECURSOS Y HERRAMIENTAS DE DESARROLLO Y DEPURACIÓN

ONLINE

Pythex

De Gabriel Rodríguez, inspirada en Rubular.

Regex101

Herramienta capaz de explicar el significado de una RE en diferentes lenguajes.

OFF-LINE

Redemo.py

Programa de demostración incluido en la propia distribución de Python (situado en

tools/scripts/redemo.py, necesita Tkinter).

Kodos

Herramienta creada por Phil Schwartz.

REFERENCIAS

Documentación Oficial de Python sobre el módulo re.

Regular Expression HOWTO de A.M. Kuchling.