expresiones regulares en python
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 NevadoTRANSCRIPT
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.
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
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’.
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”.
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.
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)
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.
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)
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']
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.
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
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
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
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)
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.
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.
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.
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”.
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.
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.']
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}'
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.'
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.