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.
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.
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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
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.
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