Es una cadena de texto que combina caracteres literales con metacaracteres, símbolos que representan un conjunto de opciones. Estas opciones son posibles combinaciones de caracteres que satisfacen determinada condición.
Se utilizan para realizar búsquedas de patrones en documentos de texto. Son una herramienta esencial en tecnologías de la información, particularmente en el procesamiento de lenguaje natural. Su uso es tan popular que la mayoría de los lenguajes de programación cuentan con integraciones para trabajar con ellas.
En una expresión regular, los metacaracteres son caracteres que a su vez representan a otros caracteres. Esto implica que, en una expresión regular, los metacaracteres no deben ser interpretados de manera literal (a menos de que sean escapados con una barra invertida: \), sino como una representación de un conjunto de patrones que se buscan en un texto. La siguiente tabla enlista todos los metacaracteres y lo que representan en una expresión regular:
Metacaracter | Encuentra: |
---|---|
* |
el caracter anterior si aparece cero o más veces |
+ |
el caracter anterior si aparece una o más veces |
? |
el caracter anterior si aparece cero o una vez |
. |
cualquier caracter |
^ |
si la expresión es el inicio de la cadena |
$ |
si la expresión es el final de la cadena |
[abc] |
si la expresión contiene cualquiera de los caracteres del conjunto |
[a-c] |
si la expresión contiene cualquiera de los caracteres del rango |
\w |
si la expresión contiene cualquiera de los caracteres de la clase predefinida |
[^abc] |
si la expresión no contiene cualquiera de los caracteres del conjutno |
\ |
el caracter siguiente como literal |
{n} |
el caracter anterior repetido n veces |
{n,m} |
el caracter anterior repetido entre n y m veces |
{n,} |
el caracter anterior repetido cuando menos n veces |
{,m} |
el caracter anterior repetido a lo más m veces |
| |
una disyunción |
(...) |
un grupo |
En una expresión regular, las clases son conjuntos de caracteres literales. Se emplean para abreviar expresiones regulares. La siguiente tabla enlista las clases predefinidas más usadas y los caracteres que representan en una expresión regular:
Clase | Encuentra: | Equivale a: |
---|---|---|
\w |
caracteres que son alfanuméricos | [a-zA-Z0-9_] |
\W |
caracteres que no alfanuméricos | [^a-zA-Z0-9_] |
\d |
caracteres que son dígitos decimales | [0-9] |
\D |
caracteres que no son dígitos decimales | [^0-9] |
\s |
caracteres que son espacios en blanco | [ \t\n\r\f\v] |
\S |
caracteres que no son espacios en blanco | [^ \t\n\r\f\v] |
Por definición, la cantidad de caracteres que *
,+
y ?
pueden encontrar es dependiente de la estructura del texto sobre el cual operan; es, por tanto, variable. Por esta razón son llamados metacaracteres cuantificadores. Su comportamiento por defecto es tomar la mayor cantidad de caracteres posible al encontrar una coincidencia. Por esta razón se les denomina greedy (codiciosos). Para evitar este comportamiento, se pueden convertir en lazy quantifiers (perezosos) agregándoles un ?
. De esta forma, minimizan la longitud de la coincidencia.
Comenzaremos aprendiendo sobre las expresiones regulares más simples posibles. Dado que las expresiones regulares se utilizan para operar en cadenas de caracteres, comenzaremos con la tarea más común: hacer coincidir caracteres.
La mayoría de letras y caracteres simplemente coincidirán. Por ejemplo, la expresión regular test coincidirá exactamente con la cadena test. (Puede habilitar un modo que no distinga entre mayúsculas y minúsculas que permitiría que este RE coincida con test o TEST también; más sobre esto más adelante.)
Hay excepciones a esta regla; algunos caracteres son especiales (metacharacters), y no coinciden. En cambio, señalan que debe coincidir con algo fuera de lo común, o afectan otras partes de la RE repitiéndolos o cambiando su significado. Gran parte de este documento está dedicado a discutir varios metacarácteres y lo que hacen.
Ahora que hemos visto algunas expresiones regulares simples, ¿cómo las usamos realmente en Python? El módulo re
proporciona una interfaz para el motor de expresiones regulares, lo que le permite compilar RE en objetos y luego realizar coincidencias con ellos.
Las expresiones regulares se compilan en objetos de patrón, que tienen métodos para diversas operaciones, como buscar coincidencias de patrones o realizar sustituciones de cadenas de caracteres.
# Realizamos la importacion de la libreria re
import re
# Creamos un objeto de patron denominado pattern,
# en la cual se copila la cadena 'hola'
pattern = re.compile(r'hola')
# veamos dicho objeto
print(pattern)
# veamos el tipo de dato de pattern
print(type(pattern))
re.compile('hola') <class 're.Pattern'>
Una vez que tiene un objeto que representa una expresión regular compilada, ¿qué hace con él? Los objetos de patrón tienen varios métodos y atributos. Aquí solo se cubrirán los más importantes; consulte los documentos re para obtener una lista completa.
Método/atributo | Objetivo |
---|---|
match() |
Determina si la RE coincide con el comienzo de la cadena de caracteres. Esto es, determinada si la regex tiene coincidencias en el comienzo del texto. |
search() |
Escanea una cadena, buscando cualquier ubicación donde coincida este RE. |
findall() |
Encuentra todas las subcadenas de caracteres donde coincide la RE y las retorna como una lista. |
finditer() |
Encuentra todas las subcadenas donde la RE coincide y las retorna como un término iterado iterator. |
match() y search() retornan None si la coincidencia no puede ser encontrada. Si tienen éxito, se retorna una instancia match object, que contiene información sobre la coincidencia: dónde comienza y termina, la subcadena de caracteres con la que coincidió, y más.
# Patron
pattern = re.compile(r'hola')
# Veamos si el patron coincide
# con la cadena 'hola' al inicio
# con la cadena 'hola'
match = pattern.match('hola')
print(match)
<re.Match object; span=(0, 4), match='hola'>
Que nos dice que se ha encontrado una coincidencia entre el patrón y la cadena 'hola'
, donde la posición del inicio de la coincidencia es el 0, y la posición final de la coincidencia es la 4, además se nos muestra la coincidencia hallada mathc='hola'
. Otro ejemplo
# Patron
pattern = re.compile(r'hola')
# Veamos si el patron coincide
# con la cadena 'hola' al inicio
match = pattern.match('hola mundo')
print(match)
<re.Match object; span=(0, 4), match='hola'>
Ahora intentamos ver coincidencias entre el patrón y la cadena 'hola mundo'
, dado que la única coincidencia entre éstos es 'hola'
, por ello se nos muestra span=(0, 4), match='hola'
. Recordemos que la función match() sólo busca coincidencias al inicio de las cadenas de texto, por lo cual, del ajemplo anterior, se busca ver si el patrón tiene coincidencias al inicio de la cadena de texto 'hola mundo'
. Así, lo siguiente nor arroja
# Cambiamos el patron
pattern = re.compile(r'mundo')
# Veamos si el patron coincide
# con la cadena 'hola mundo' al inicio
match = pattern.match('hola mundo')
print(match)
None
pues el patrón no tuvo coincidencias al inicio de la cadena 'hola mundo'
. Ahora bien, podemos especificar la posición de inicio de la búsqueda. Por ejmplo, en 'hola mundo'
, la palabra mundo
comienza en la posición 5, de donde
# Cambiamos el patron
pattern = re.compile(r'mundo')
# Veamos si el patron coincide
# con la cadena 'hola mundo' a partir
# de la posicion 5
match = pattern.match('hola mundo', 5)
print(match)
<re.Match object; span=(5, 10), match='mundo'>
Por lo cual la coincidencia fue hallada.
# Cambiamos el patron
pattern = re.compile(r'mundo')
# Veamos si el patron coincide
# con la cadena 'hola mundo' a partir
# de la posicion 4
match = pattern.match('hola mundo', 4)
print(match)
None
Luego, podemos agregar un parámetro adicional especificando la longitud de los caracteres que tomaremos de nuestro documento. Por ejemplo
pattern = re.compile(r'hola')
match = pattern.match(' hola ', 1, 3)
print(match)
<re.Match object; span=(1, 5), match='hola'>
especificamos que queremos comenzar a hallar la coincidencia a partir del índice 1, pero independientemente de ese índice, tomaremos sólo 3 caracteres (se cuenta desde el inicio), esto es, consideraremos entonces la cadena 'ho'
, por lo cual no hay coincidencias con el patrón 'hola'
. Pero, por otro lado
pattern = re.compile(r'hola')
match = pattern.match(' hola ', 1, 5)
print(match)
<re.Match object; span=(1, 5), match='hola'>
sí halla una coincidencia.
Por otro lado, recoredemos que el cuantificador ^
busca la coincidencia si el patrón es el inicio de la cadena. De tal manera
pattern = re.compile(r'^hola')
match = pattern.match(' hola ')
print(match)
None
veamos si el patrón 'hola'
es el inicio de la cadena ' hola '
, lo cual no ocurre pues tenemos al inicio un espacio en blanco. Lo anterior no cambia a pesar de que cambiemos el índice inicial de búsqueda, es decir, si buscamos la coincidencia con la cadena ' hola '
a partir del primer índice 'hola '
no obtendremos una coincidencia pues el documento (es decir, la cadena) continúa siendo ' hola '
a pesar de que consideremos la búsqueda a partir del índice 1. Así, con el cunatificador ^
busca la coincidencia al inicio del documento
match = pattern.match(' hola ', 1)
print(match)
None
y por ello nos arroja que no hubo coincidencias. Si ahora modificamos el documento (la cadena) tendremos que
match = pattern.match(' hola '[1:])
print(match)
<re.Match object; span=(0, 4), match='hola'>
sí se ha hallado una coincidencia.
Ahora no sólo buscaremos coincidencias al inicio de un documento, ahora veremos coincidencias en cualquier parte del documento. Por ejemplo
# Definimos el patron
pattern = re.compile(r'hola')
# Vemos si el patron aparece en el siguiente documento
match = pattern.search('adios\nhola')
print(match)
<re.Match object; span=(6, 10), match='hola'>
veamos que sí hubo coincidencias a partir del índice 6 al 10, donde el salto de línea cuenta como una "caracter". Podríamos intentar ver si el documento anterior comienza con la cadena 'hola'
, lo cual no pasa
pattern = re.compile(r'^hola')
match = pattern.search('adios\nhola')
print(match)
None
no obstante, el documento si comienza con 'hola'
en la segunda línea. Lo anterior lo podemos implementar como
pattern = re.compile(r'hola', re.M)
match = pattern.search('adios\nhola')
print(match)
<re.Match object; span=(6, 10), match='hola'>
donde agregamos una "bandera" (concepto que veremos más adelante) que indica que nuestro documento es multilínea (re.M
), por lo cual sí se ha hallado coincidencia.
Indaguemos ahora en un ejemplo más elaborado. Consideraremos el siguiente patrón ' ?[Ss]h[a-z]+ '
el cual no dice:
' ?
: Espacio en blanco si aparece cero o una vez.[Ss]
: La letra ese mayúscula o ese minúscula.h
: Forzosamente la letra h.[a-z]+
: Cualquiere letra minúscula si aparece una o más veces'
: Forzosamente un espacio en blanco.De tal manera, si consideramos el documento 'Shell scripting should not be shameful, right Shane?'
, basados en el anterior patrón descrito tendremos por coincidencias únicamente a Shell
, ya que la búsqueda nos arroja la primera coincidencia que encuentra
pattern = re.compile(r' ?[Ss]h[a-z]+ ')
document = 'Shell scripting should not be shameful, right Shane?'
match = pattern.search(document)
print(match)
<re.Match object; span=(0, 6), match='Shell '>
Si por ejemplo modificamos el índice de búsqueda y la longitud de los caracteres
match = pattern.search(document, 10, 23)
print(match)
<re.Match object; span=(15, 23), match=' should '>
obtenemos la siguiente coincidencia que esperaríamos. Dado que especificamos un espacio en blanco al final de nuestro patrón, entonces la coincidencia anterior fue en realidad la última coincidencia
match = pattern.search(document, 25)
print(match)
None
Flag | Equivale a: | Efecto sobre la expresión regular |
---|---|---|
re.I |
re.IGNORECASE |
No hay distinción entre mayúsculas y minúsculas. |
re.M |
re.MULTILINE |
Los inicios y finales de línea equivalen a inicio y final de cadena respectivamente. |
re.S |
re.DOTALL |
El metacaracter . incluye los saltos de línea. |
Ya habíamos visto la bandera re.M
para que nuestras búsquedas también se hicierán las líneas que no son necesariamente la primera. Luego, podemos utilizar más de una bandera al mismo tiempo escribiendo |
para trabajar con más de una de ellas:
pattern = re.compile(r'^mundo', re.I | re.M)
match = pattern.search('hola\nMUNDO')
print(match)
<re.Match object; span=(5, 10), match='MUNDO'>
donde en nuestro patrón no se distinguirá entre mayúsculas y minúsculas y la búsqueda se efectuará multilínea. Por otro lado consideramos
pattern = re.compile('.+')
document = """Porque el corazón es el que se arruga,
pero el corazón no.
Palabras inmortales de Miguel Luis.
Miguel Luis
abril 2003
"""
match = pattern.search(document)
print(match.group())
Porque el corazón es el que se arruga,
que nos arroja únicamente la primer línea de nuestro documento, lo cual arreglamos al utilizar la bandera re.S
como sigue:
pattern = re.compile('.+', re.S)
match = pattern.search(document)
print(match.group())
Porque el corazón es el que se arruga, pero el corazón no. Palabras inmortales de Miguel Luis. Miguel Luis abril 2003
Consideremos el siguiente ejemplo.
document = 'I prophecy disaster, shall this sentece be tokenized.'
pattern = re.compile(r'()\w+()')
match = pattern.search(document)
print(match)
<re.Match object; span=(0, 1), match='I'>
donde nuestro patrón de búsqueda es cualquier palabra entre espacios en blanco, de modo que, a partir de nuestro documento, tendríamos las coincidencias: prophecy
, shall
, this
, be
y sentece
. Pero re.search
nos arroja la primer coincidencia que encuentra y no todas. Para que se nos arrojen todas las coincidencias dado un patrón utilizaremos re.findall
en vez de re.search
, por lo cual escribimos
document = 'I prophecy disaster, shall this sentece be tokenized.'
pattern = re.compile(r' \w+ ')
match_list = pattern.findall(document)
print(match_list)
[' prophecy ', ' shall ', ' sentece ']
(nota que las coincidencias son palabras junto con sus espacio en blanco) donde esperabamos que se nos regresarán más coincidencias. No obstante, pudimos ver la esencia de re.findall
la cual es que se nos retornan todas las coincidencias que se encuentren. Ahora bien, arreglaremos nuestro patrón para obtener todas las coincidencias que habíamos hablando antes. El problema de lo anterior es que, por ejemplo, shall
está ocupando el espacio en blanco de la derecha de modo que this
no tiene un espacio en blanco a la izquierda y por ello no aparece en nuestras coincidencias, de mismo modo, be
no aparece en las coincidencias. Veamos que los cuantificadores tampoco nos ayudarán en esta situación. Para resolver el problema requerimos de:
Son partes de una expresión regular que contribuyen al patrón de búsqueda pero no son parte del texto coincidente. Pueden ir al inicio de la expresión regular (lookbehind) o al final de ésta (lookahead).
Así, si consideremos ahora el patrón
pattern = re.compile(r'(?<= )\w+(?= )')
tenemos que (?<= )
es el lookbehind, el cual especificamos colocando (?<=)
, y (?= )
es el lookahead, el cual especificamos colocando (?=)
, de nuestro patrón. Lo que hacemos es que:
(?<= )\w+
: Buscaremos palabras que tengan un espacio a la izquierda, pero la coincidencia será solamente la palabra sin dicho espacio. Por ejemplo, se buscará la palabra shall
pues tiene un espacio a la izquierda, pero la coincidencia será shall
sin el espacio a la izquierda.
\w+(?= )
: Buscaremos palabras que tengan un espacio a la derecha, pero la coincidencia será solamente la palabra sin dicho espacio. Por ejemplo, se buscará la palabra shall
pues tiene un espacio a la derecha, pero la coincidencia será shall
sin el espacio a la derecha.
De tal manera, buscaremos a todas las palabras que tenga espacio a la izquierda y derecha, pero la coincidencia serán dichas palabras sin los espacios:
document = 'I prophecy disaster, shall this sentece be tokenized.'
# lookbehind | lookahead
pattern = re.compile(r'(?<= )\w+(?= )')
match_list = pattern.findall(document)
print(match_list)
['prophecy', 'shall', 'this', 'sentece', 'be']
nota que las coincidencias son palabras sin espacio en blanco.
Por otro lado, muy parecido a re.findall
es re.finditer
, donde ahora el resultado no son todas las coincidencias como vimos antes, más bien, el resultado es un iterable que contiene a todas las coincidencias, por ejemplo
document = 'I prophecy disaster, shall this sentece be tokenized.'
pattern = re.compile(r'(?<= )\w+(?= )')
match_iter = pattern.finditer(document)
print(match_iter)
<callable_iterator object at 0x000001F671849550>
vemos que el resultado es un iterable. Para acceder a todas las coincidencias podemos utilizar un bucle for
for i, match in enumerate(match_iter):
print(f'match {i}:', match)
match 0: <re.Match object; span=(2, 10), match='prophecy'> match 1: <re.Match object; span=(21, 26), match='shall'> match 2: <re.Match object; span=(27, 31), match='this'> match 3: <re.Match object; span=(32, 39), match='sentece'> match 4: <re.Match object; span=(40, 42), match='be'>
# O alternativamente, podemos acceder directo a la coincidencia
# escribiendo match[0]
for i, match in enumerate(match_iter):
print(f'match {i}:', match[0])
match 0: prophecy match 1: shall match 2: this match 3: sentece match 4: be
Dado un documento, en general cualquier cadena de texto, re.split
divide dicho documento a partir de un patrón que le demos. Por ejemplo
document = 'Barbara,Celarent,Darii,Ferio,Cesare,Camestres,Festino,Baroco,Darapti,Disamis,Datisi,Felapton,Ferison,Bocardo'
syl_figs = document.split(",")
print(syl_figs)
['Barbara', 'Celarent', 'Darii', 'Ferio', 'Cesare', 'Camestres', 'Festino', 'Baroco', 'Darapti', 'Disamis', 'Datisi', 'Felapton', 'Ferison', 'Bocardo']
podemos dividir un documento a partir de las comas, donde los elementos obtenidos de dicha división son almacenados en una lista. Si tenemos otro tipo de documento más problemático como
Barbara;Celarent.Darii|Ferio°Cesare Camestres estino_Baroco+Darapti¿Disamis#Datisi%Felapton&Ferison!Bocardo
no será tan simple separar las palabras. En realidad si es simple si empleamos expresiones regulares, donde nuestro separador serán los caracteres que no son alfanumérico (\W
):
document = 'Barbara;Celarent.Darii|Ferio°Cesare Camestres-Festino_Baroco+Darapti¿Disamis#Datisi%Felapton&Ferison!Bocardo'
pattern = re.compile(r'\W')
syl_figs = pattern.split(document)
print(syl_figs)
['Barbara', 'Celarent', 'Darii', 'Ferio', 'Cesare', 'Camestres', 'Festino_Baroco', 'Darapti', 'Disamis', 'Datisi', 'Felapton', 'Ferison', 'Bocardo']
Podemos indicar además el número de palabras que queremos separar. Por ejemplo, del documento anterior y dado el separador \W
, queremos sólo 5 separaciones:
syl_figs = pattern.split(document, 5)
print(syl_figs)
['Barbara', 'Celarent', 'Darii', 'Ferio', 'Cesare', 'Camestres-Festino_Baroco+Darapti¿Disamis#Datisi%Felapton&Ferison!Bocardo']
Consideremos el documento de abajo, el patrón de búsqueda que realizamos son todas aquellas letras que tengan acento
quote = 'Vine a Comala porque me dijeron que aquí vivía mi padre, un tal Pedro Páramo.'
pattern = re.compile(r'[áéíóú]')
results_quote = pattern.findall(quote)
print(results_quote)
['í', 'í', 'á']
supongamos ahora que queremos que nuestro documento no tenga palabras con acentos, para ello utilizaremos re.sub
que sustituirá la coincidencia encontrada con algún valor que indiquemos
quote = 'Vine a Comala porque me dijeron que aquí vivía mi padre, un tal Pedro Páramo.'
pattern = re.compile(r'[áéíóú]')
# valor | docum-
# de | to
# susti-|
# tucion|
cleansed_quote = pattern.sub('i', quote)
print(cleansed_quote)
Vine a Comala porque me dijeron que aqui vivia mi padre, un tal Pedro Piramo.
donde sustituimos las coincidencias ['í', 'í', 'á']
con la letra i
, pero vemos un problema pues pasamos de páramo
a piramo
, lo cual es algo que no queremos. Para arreglar lo anterior nos tendremos que valer de una función lambda
y de un diccionario.
# Primero creamos un diccionario con los valores que
# queremos sustituir y los valores de sustitucion
replace_dict = {
'á': 'a',
'é': 'e',
'í': 'i',
'ó': 'o',
'ú': 'u'
}
después, dada una coincidencia (una letra con acento), dicha coincidencia será reemplazada por su valor correspondiente en el diccionario anterior. Por ejemplo, si tenemos la coincidencia 'í'
, entonces su valor asociado en el diccionario es 'i'
, por lo que reemplazaremos la coincidencia con el valor replace_dict['í']
. Así, nuestra función lambda
actuará, dada una coincidencia, digámosle match
, devolverá replace_dict[match[0]]
que es el valor de sustitución referente a dicha coincidencia. Por ende
cleansed_quote = pattern.sub(lambda match: replace_dict[match[0]], quote)
print(cleansed_quote)
Vine a Comala porque me dijeron que aqui vivia mi padre, un tal Pedro Paramo.
vemos que las sustituciones se han realizado correctamente. De manera similar re.subn
realiza sustituciones, pero ahora nos regresará adicionalmente el número de sustituciones que fueron realizadas:
quote = 'Vine a Comala porque me dijeron que aquí vivía mi padre, un tal Pedro Páramo.'
pattern = re.compile(r'[áéíóú]')
replace_dict = {
'á': 'a',
'é': 'e',
'í': 'i',
'ó': 'o',
'ú': 'u'
}
# documento | numero de
# modificado | sustituciones
cleansed_quote, num_sust = pattern.subn(lambda match: replace_dict[match[0]], quote)
# documento modificado
print(cleansed_quote)
# numero de sustituciones realizadas
print(num_sust)
Vine a Comala porque me dijeron que aqui vivia mi padre, un tal Pedro Paramo. 3
Consideremos por ejemplo
pattern = re.compile(r'\w+, \w+: [\w ]+')
document = 'Rulfo, Juan: Pedro Páramo'
match = pattern.search(document)
print(match)
<re.Match object; span=(0, 25), match='Rulfo, Juan: Pedro Páramo'>
donde:
\w+,
cualquier caracter alfanumérico que aparece de una a más veces, después una coma y después un espacio en blanco; después \w+:
cualquier caracter alfanumérico que aparece una o más veces, después dos puntos y después un espacio; continuando, [\w ]+
si la expresión contiene cualquier caracter alfanumérico que aparece una o más veces o un espacio en blanco. Así, el match es el documento completo.Veamos que es lo que nos arroja el siguiente comando referente a los grupos
# Todos los grupos de nuestro match
print(match.group())
# El primer grupo de nuestro match
print(match.group(0))
Rulfo, Juan: Pedro Páramo Rulfo, Juan: Pedro Páramo
# Ya no tenemos mas grupos
print(match.group(1))
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Input In [9], in <cell line: 2>() 1 # Ya no tenemos mas grupos ----> 2 print(match.group(1)) IndexError: no such group
lo cual nos dice que en nuestro match sólo tenemos un grupo, el cual es el documento completo, esto es, siempre tendremos al menos un grupo, y dicho grupo es siempre el documento completo. Luego, notemos que al formar los siguientes grupos
pattern = re.compile(r'(\w+), (\w+): ([\w ]+)')
document = 'Rulfo, Juan: Pedro Páramo'
match = pattern.search(document)
# La coincidencia es todo el documento
print(match)
# Todos los grupos del match
print(match.group())
<re.Match object; span=(0, 25), match='Rulfo, Juan: Pedro Páramo'> Rulfo, Juan: Pedro Páramo
# Pero tenemos mas grupos:
# El grupo trivial: todo el documento
print(match.group(0))
# El grupo dos
print(match.group(1))
# El grupo tres
print(match.group(2))
# El grupo cuatro
print(match.group(3))
Rulfo, Juan: Pedro Páramo Rulfo Juan Pedro Páramo
Podemos asignarles nombres a los grupos, para lo cual escribiremos ?P<nombre del grupo>
y acceder a ellos mediante el método group()
con dicho nombre que el asignamos, en vez de acceder a ellos mediante índices como le hicimos antes.
pattern = re.compile(r'(?P<Apellido>\w+), (?P<Nombre>\w+): (?P<Novela>[\w ]+)')
match = pattern.search(document)
# Accedemos a los grupos de acuerdo a los nombres que les colocamos antes
print(match.group('Apellido'))
print(match.group('Nombre'))
print(match.group('Novela'))
# O tambien mediante indices
print(match.group(1))
# Veamos si el grupo 'Novela' es el grupo de indice 3
print(match.group('Novela') == match.group(3))
Rulfo Juan Pedro Páramo Rulfo True
Podemos obtener los nombres de los grupos con los elementos de éstos en un diccionario:
print(match.groupdict())
{'Apellido': 'Rulfo', 'Nombre': 'Juan', 'Novela': 'Pedro Páramo'}
Podemos utilizar banderas, por ejemplo para un documento con varias líneas:
pattern = re.compile(r'(\w+), (\w+): ([\w ]+)', re.M)
document = """
Rulfo, Juan: Pedro Páramo
Garro, Elena: Recuerdos del porvenir
Arreola, José: La feria
Puga, María Luisa: Las posibilidades del odio
Revueltas, José: El apando
Castellanos, Rosario: Balún Canán
"""
# Buscamos todos los elementos que cumplan con el patron
# en cada una de las lineas del documento
iter_pattern = pattern.finditer(document)
# Convertimos a lista todos los matches hallados
match_list = list(iter_pattern)
match_list
[<re.Match object; span=(1, 26), match='Rulfo, Juan: Pedro Páramo'>, <re.Match object; span=(27, 63), match='Garro, Elena: Recuerdos del porvenir'>, <re.Match object; span=(64, 87), match='Arreola, José: La feria'>, <re.Match object; span=(134, 160), match='Revueltas, José: El apando'>, <re.Match object; span=(161, 194), match='Castellanos, Rosario: Balún Canán'>]
# Para cada match de la lista, veamos todos los grupos
# para lo cual utilizamos el metodo groups()
for match in match_list:
print(match.groups())
('Rulfo', 'Juan', 'Pedro Páramo') ('Garro', 'Elena', 'Recuerdos del porvenir') ('Arreola', 'José', 'La feria') ('Revueltas', 'José', 'El apando') ('Castellanos', 'Rosario', 'Balún Canán')
# Para cada match de la lista, veamos el grupo de indice 1
for match in match_list:
print(match.group(1))
Rulfo Garro Arreola Revueltas Castellanos
# Para cada match de la lista, veamos el grupo de indice 2
for match in match_list:
print(match.group(2))
Juan Elena José José Rosario
Para finalizar podemos ver el siguiente ejemplo, donde en el patrón tendremos tres grupos: uno para el nombre, otro para el correo electrónico y otro para el número de cuenta:
document = """
Ayudante: Joaquín
Correo: vok@ciencias.unam.mx
Num de cta: 123123123
"""
pattern = re.compile(r'Ayudante: (\w+)\nCorreo: ([\w\._-]+@ciencias.unam.mx)\nNum de cta: (\d+)')
match = pattern.search(document)
print(match)
<re.Match object; span=(1, 69), match='Ayudante: Joaquín\nCorreo: vok@ciencias.unam.mx\n>
Ahora, una vez que tenemos los grupos podemos utilizar el método expand()
, el cual básicamente actúa como un sub()
pero sobre grupos. Antes, para hacer referencia a grupos en nuestra coincidencia utilizamos el metacaracter \g
seguido del índice del grupo al cual haremos referencia: por ejemplo \g<1>
hará referencia al grupo de índice cero. Así, lo siguiente
csv_doc = match.expand(r'\g<3>,\g<2>,\g<1>')
print(csv_doc)
123123123,vok@ciencias.unam.mx,Joaquín
hace que se nos retorne el contenido del grupo 3 coma, 2 coma y 1 respectivamente.