re — Expresiones Regulares

Propósito:Buscar dentro y cambiar texto usando patrones formales.

Las expresiones regulares son patrones de coincidencia de texto descritos con una sintaxis formal. Los patrones se interpretan como un conjunto de instrucciones, que luego se ejecutan con una cadena como entrada para producir una subconjunto de coincidencia o una versión modificada del original. El término «expresiones regulares» con frecuencia se acorta en conversación a «regex» o «regexp» . Las expresiones pueden incluir correspondencia de texto literal, repetición, composición de patrones, ramificación y otras reglas sofisticadas. Una gran cantidad de problemas de análisis son más fáciles de resolver con una expresión regular que mediante la creación de un analizador léxico de propósito especial y un analizador sintáctico.

Las expresiones regulares se usan normalmente en aplicaciones que implican procesamiento de una gran cantidad de texto. Por ejemplo, se usan comúnmente como patrones de búsqueda en los programas de edición de texto utilizados por los desarrolladores, incluido vi, emacs e IDEs modernos. También son una parte integral de utilidades de Unix de línea de comandos como sed, grep y awk. Muchos lenguajes de programación incluyen soporte para expresiones regulares en el sintaxis (Perl, Ruby, Awk y Tcl). Otros lenguajes, como C, C ++, y Python, admiten expresiones regulares a través de bibliotecas de extensión.

Existen múltiples implementaciones de código abierto de expresiones regulares, cada una compartiendo una sintaxis común pero con diferentes extensiones o modificaciones a sus características avanzadas. La sintaxis utilizada en el módulo re en Python se basa en la sintaxis utilizada para las expresiones regulares en Perl, con algunas mejoras específicas de Python.

Nota

Aunque la definición formal de «expresión regular» está limitada a las expresiones que describen los idiomas regulares, algunas de las las extensiones soportadas por re van más allá de describir idiomas. El término «expresión regular» se usa aquí en un sentido general para significar cualquier expresión que pueda ser evaluada por el módulo re de Python.

Encontrar patrones en el texto

El uso más común para re es buscar patrones en texto. La función search() toma el patrón y el texto para escanear, y devuelve un objeto Match cuando se encuentra el patrón. Si el patrón no se encuentra, search() devuelve None.

Cada objeto Match contiene información sobre la naturaleza de la coincidencia, incluida la cadena de entrada original, la expresión regular utilizada, y la ubicación dentro de la cadena original donde el patrón ocurre.

re_simple_match.py
import re

pattern = 'this'
text = 'Does this text match the pattern?'

match = re.search(pattern, text)

s = match.start()
e = match.end()

print('Found "{}"\nin "{}"\nfrom {} to {} ("{}")'.format(
    match.re.pattern, match.string, s, e, text[s:e]))

Los métodos start() y end() dan los índices a la cadena que muestra dónde se produce el texto que coincide con el patrón.

$ python3 re_simple_match.py

Found "this"
in "Does this text match the pattern?"
from 5 to 9 ("this")

Compilar Expressiones

Aunque re incluye funciones a nivel de módulo para trabajar con expresiones como cadenas de texto, es más eficiente compilar las expresiones que un programa usa con frecuencia. La función compile() convierte una cadena de expresión en un RegexObject.

re_simple_compiled.py
import re

# Precompile the patterns
regexes = [
    re.compile(p)
    for p in ['this', 'that']
]
text = 'Does this text match the pattern?'

print('Text: {!r}\n'.format(text))

for regex in regexes:
    print('Seeking "{}" ->'.format(regex.pattern),
          end=' ')

    if regex.search(text):
        print('match!')
    else:
        print('no match')

Las funciones de nivel de módulo mantienen un caché de expresiones compiladas pero el tamaño de la memoria caché es limitado y usar expresiones compiladas directamente evita la sobrecarga asociada con la búsqueda en el caché. Otra ventaja de usar expresiones compiladas es que precompilando todas las expresiones cuando se carga el módulo, el trabajo de compilación se mueve ahora al inicio de la aplicación, en lugar de ocurrir en un punto donde el programa puede estar respondiendo a una acción del usuario.

$ python3 re_simple_compiled.py

Text: 'Does this text match the pattern?'

Seeking "this" -> match!
Seeking "that" -> no match

Coincidencias múltiples

Hasta ahora, los patrones de ejemplo han usado search() para buscar instancias únicas de cadenas de texto literales. La función findall() devuelve todas las subcadenas de la entrada que coinciden con el patrón sin superposición.

re_findall.py
import re

text = 'abbaaabbbbaaaaa'

pattern = 'ab'

for match in re.findall(pattern, text):
    print('Found {!r}'.format(match))

Esta cadena de entrada de ejemplo incluye dos instancias de ab.

$ python3 re_findall.py

Found 'ab'
Found 'ab'

La función finditer() devuelve un iterador que produce instancias Match en lugar de las cadenas devueltas por findall().

re_finditer.py
import re

text = 'abbaaabbbbaaaaa'

pattern = 'ab'

for match in re.finditer(pattern, text):
    s = match.start()
    e = match.end()
    print('Found {!r} at {:d}:{:d}'.format(
        text[s:e], s, e))

Este ejemplo encuentra las mismas dos apariciones de ab, y la instancia Match muestra dónde se encuentran en la entrada original.

$ python3 re_finditer.py

Found 'ab' at 0:2
Found 'ab' at 5:7

Sintaxis de patrones

Las expresiones regulares admiten patrones más potentes que la cadena de texto simple. Los patrones pueden repetirse, pueden anclarse a diferentes ubicaciones lógicas dentro de la entrada, y se pueden expresar en forma compacta que no requiere que todos los caracteres literales estén presentes en el patrón. Todas estas características se utilizan al combinar valores literales de texto con metacaracteres que son parte de la sintaxis expresión regular de patrón implementada por re.

re_test_patterns.py
import re


def test_patterns(text, patterns):
    """Given source text and a list of patterns, look for
    matches for each pattern within the text and print
    them to stdout.
    """
    # Look for each pattern in the text and print the results
    for pattern, desc in patterns:
        print("'{}' ({})\n".format(pattern, desc))
        print("  '{}'".format(text))
        for match in re.finditer(pattern, text):
            s = match.start()
            e = match.end()
            substr = text[s:e]
            n_backslashes = text[:s].count('\\')
            prefix = '.' * (s + n_backslashes)
            print("  {}'{}'".format(prefix, substr))
        print()
    return


if __name__ == '__main__':
    test_patterns('abbaaabbbbaaaaa',
                  [('ab', "'a' followed by 'b'"),
                   ])

Los siguientes ejemplos usarán test_patterns() para explorar cómo las variaciones en los patrones cambian la forma en que coinciden con el mismo texto de entrada. El resultado muestra el texto de entrada y el rango de subcadena de cada porción de la entrada que coincide con el patrón.

$ python3 re_test_patterns.py

'ab' ('a' followed by 'b')

  'abbaaabbbbaaaaa'
  'ab'
  .....'ab'

Repetición

Hay cinco formas de expresar la repetición en un patrón. Un patrón seguido por el meta-carácter * se repite cero o más veces (Permitiendo que un patrón se repita cero veces significa que no necesita aparecer en absoluto para que coincida). Si el * se reemplaza con +, el patrón debe aparecer al menos una vez. Usar ? `` significa que el patrón aparece cero o una vez. Para un número específico de apariciones, usa ``{m} después de el patrón, donde m es el número de veces que el patrón debería repetirse. Finalmente, para permitir un número variable pero limitado derepeticiones, usa {m, n}, donde m es el número mínimo de repeticiones y n es el máximo. Sin n ({m,}) significa que el valor debe aparecer al menos m veces, sin máximo.

re_repetition.py
from re_test_patterns import test_patterns

test_patterns(
    'abbaabbba',
    [('ab*', 'a followed by zero or more b'),
     ('ab+', 'a followed by one or more b'),
     ('ab?', 'a followed by zero or one b'),
     ('ab{3}', 'a followed by three b'),
     ('ab{2,3}', 'a followed by two to three b')],
)

Hay más coincidencias para ab* y ab? Que ab+.

$ python3 re_repetition.py

'ab*' (a followed by zero or more b)

  'abbaabbba'
  'abb'
  ...'a'
  ....'abbb'
  ........'a'

'ab+' (a followed by one or more b)

  'abbaabbba'
  'abb'
  ....'abbb'

'ab?' (a followed by zero or one b)

  'abbaabbba'
  'ab'
  ...'a'
  ....'ab'
  ........'a'

'ab{3}' (a followed by three b)

  'abbaabbba'
  ....'abbb'

'ab{2,3}' (a followed by two to three b)

  'abbaabbba'
  'abb'
  ....'abbb'

Al procesar una instrucción de repetición, re usualmente consume la mayor cantidad posible de información mientras coincide con el patrón. Este comportamiento llamado codicioso puede resultar en un menor número de Coincidencias individuales, o las coincidencias pueden incluir más texto de entrada que el desado. La codicia puede apagarse siguiendo la instrucción repetición con ?.

re_repetition_non_greedy.py
from re_test_patterns import test_patterns

test_patterns(
    'abbaabbba',
    [('ab*?', 'a followed by zero or more b'),
     ('ab+?', 'a followed by one or more b'),
     ('ab??', 'a followed by zero or one b'),
     ('ab{3}?', 'a followed by three b'),
     ('ab{2,3}?', 'a followed by two to three b')],
)

Deshabilitando el consumo codicioso de la entrada para cualquiera de los patrones donde se permiten cero ocurrencias de b significa la subcadena coincidente no incluye ningún caracter b.

$ python3 re_repetition_non_greedy.py

'ab*?' (a followed by zero or more b)

  'abbaabbba'
  'a'
  ...'a'
  ....'a'
  ........'a'

'ab+?' (a followed by one or more b)

  'abbaabbba'
  'ab'
  ....'ab'

'ab??' (a followed by zero or one b)

  'abbaabbba'
  'a'
  ...'a'
  ....'a'
  ........'a'

'ab{3}?' (a followed by three b)

  'abbaabbba'
  ....'abbb'

'ab{2,3}?' (a followed by two to three b)

  'abbaabbba'
  'abb'
  ....'abb'

Conjuntos de caractéres

Un conjunto de caracteres es un grupo de caracteres, cualquiera de los cuales puede coincidir en ese punto en el patrón. Por ejemplo, [ab] coincidiría cualquiera a o b.

re_charset.py
from re_test_patterns import test_patterns

test_patterns(
    'abbaabbba',
    [('[ab]', 'either a or b'),
     ('a[ab]+', 'a followed by 1 or more a or b'),
     ('a[ab]+?', 'a followed by 1 or more a or b, not greedy')],
)

La forma codiciosa de la expresión (a[ab]+) consume la totalidad cadena porque la primera letra es a y cada carácter subsecuente es a o b.

$ python3 re_charset.py

'[ab]' (either a or b)

  'abbaabbba'
  'a'
  .'b'
  ..'b'
  ...'a'
  ....'a'
  .....'b'
  ......'b'
  .......'b'
  ........'a'

'a[ab]+' (a followed by 1 or more a or b)

  'abbaabbba'
  'abbaabbba'

'a[ab]+?' (a followed by 1 or more a or b, not greedy)

  'abbaabbba'
  'ab'
  ...'aa'

Un conjunto de caracteres también se puede usar para excluir caracteres específicos. El signo de intercalación (^) significa buscar caracteres que no están en el conjunto que sigue.

re_charset_exclude.py
from re_test_patterns import test_patterns

test_patterns(
    'This is some text -- with punctuation.',
    [('[^-. ]+', 'sequences without -, ., or space')],
)

Este patrón encuentra todas las subcadenas que no contienen los caracteres -, ., o un espacio.

$ python3 re_charset_exclude.py

'[^-. ]+' (sequences without -, ., or space)

  'This is some text -- with punctuation.'
  'This'
  .....'is'
  ........'some'
  .............'text'
  .....................'with'
  ..........................'punctuation'

A medida que los conjuntos de caracteres crecen, escribir cada carácter que debería (o no debería) coincidir se vuelve tedioso. Un formato más compacto usando los rangos de caracteres se pueden usar para definir un conjunto de caracteres para incluir todos los caracteres contiguos entre los puntos de inicio y de parada.

re_charset_ranges.py
from re_test_patterns import test_patterns

test_patterns(
    'This is some text -- with punctuation.',
    [('[a-z]+', 'sequences of lowercase letters'),
     ('[A-Z]+', 'sequences of uppercase letters'),
     ('[a-zA-Z]+', 'sequences of letters of either case'),
     ('[A-Z][a-z]+', 'one uppercase followed by lowercase')],
)

Aquí el rango a-z incluye las letras ASCII en minúsculas, y el rango A-Z incluye las letras mayúsculas ASCII. Los rangos también se pueden combinar en un solo conjunto de caracteres.

$ python3 re_charset_ranges.py

'[a-z]+' (sequences of lowercase letters)

  'This is some text -- with punctuation.'
  .'his'
  .....'is'
  ........'some'
  .............'text'
  .....................'with'
  ..........................'punctuation'

'[A-Z]+' (sequences of uppercase letters)

  'This is some text -- with punctuation.'
  'T'

'[a-zA-Z]+' (sequences of letters of either case)

  'This is some text -- with punctuation.'
  'This'
  .....'is'
  ........'some'
  .............'text'
  .....................'with'
  ..........................'punctuation'

'[A-Z][a-z]+' (one uppercase followed by lowercase)

  'This is some text -- with punctuation.'
  'This'

Como un caso especial de un conjunto de caracteres, el meta-caracteres punto (.), indica que el patrón debe coincidir con cualquier carácteren en esa posición.

re_charset_dot.py
from re_test_patterns import test_patterns

test_patterns(
    'abbaabbba',
    [('a.', 'a followed by any one character'),
     ('b.', 'b followed by any one character'),
     ('a.*b', 'a followed by anything, ending in b'),
     ('a.*?b', 'a followed by anything, ending in b')],
)

Combinar el punto con la repetición puede dar como resultado coincidencias muy largas, a menos quese use la forma no codiciosa.

$ python3 re_charset_dot.py

'a.' (a followed by any one character)

  'abbaabbba'
  'ab'
  ...'aa'

'b.' (b followed by any one character)

  'abbaabbba'
  .'bb'
  .....'bb'
  .......'ba'

'a.*b' (a followed by anything, ending in b)

  'abbaabbba'
  'abbaabbb'

'a.*?b' (a followed by anything, ending in b)

  'abbaabbba'
  'ab'
  ...'aab'

Códigos de escape

Una representación aún más compacta usa códigos de escape para varios conjuntos de caracteres predefinidos. Los códigos de escape reconocidos por re se enumeran en the table below.

Códigos de escape de expresiones regulares
Código Significado
\d un dígito
\D un on-dígito
\s espacio en blanco (tabulador, espacio, línea nueva, etc.)
\S no espacio en blanco
\w alfanumérico
\W no-alfanumérico

Nota

Los escapes son indicados por el prefijo del caracter con una barra invertida (\). Desafortunadamente, una barra invertida debe ser escapada en sí misma en cadenas de Python, y eso resulta en expresiones difíciles de leer. Usar cadenas raw, que se crean al ponerle un prefijo al literal valor con r, elimina este problema y mantiene la legibilidad.

re_escape_codes.py
from re_test_patterns import test_patterns

test_patterns(
    'A prime #1 example!',
    [(r'\d+', 'sequence of digits'),
     (r'\D+', 'sequence of non-digits'),
     (r'\s+', 'sequence of whitespace'),
     (r'\S+', 'sequence of non-whitespace'),
     (r'\w+', 'alphanumeric characters'),
     (r'\W+', 'non-alphanumeric')],
)

Estas expresiones de muestra combinan códigos de escape con repetición para encontrar secuencias de caracteres similares en la cadena de entrada.

$ python3 re_escape_codes.py

'\d+' (sequence of digits)

  'A prime #1 example!'
  .........'1'

'\D+' (sequence of non-digits)

  'A prime #1 example!'
  'A prime #'
  ..........' example!'

'\s+' (sequence of whitespace)

  'A prime #1 example!'
  .' '
  .......' '
  ..........' '

'\S+' (sequence of non-whitespace)

  'A prime #1 example!'
  'A'
  ..'prime'
  ........'#1'
  ...........'example!'

'\w+' (alphanumeric characters)

  'A prime #1 example!'
  'A'
  ..'prime'
  .........'1'
  ...........'example'

'\W+' (non-alphanumeric)

  'A prime #1 example!'
  .' '
  .......' #'
  ..........' '
  ..................'!'

Para encontrar lo caracteres que son parte de la sintaxis de expresión regular, escapa de los caractéres en el patrón de búsqueda.

re_escape_escapes.py
from re_test_patterns import test_patterns

test_patterns(
    r'\d+ \D+ \s+',
    [(r'\\.\+', 'escape code')],
)

El patrón en este ejemplo escapa a la barra invertida y más caracteres, ya que ambos son meta-caracteres y tienen un significado especial en una expresión.

$ python3 re_escape_escapes.py

'\\.\+' (escape code)

  '\d+ \D+ \s+'
  '\d+'
  .....'\D+'
  ..........'\s+'

Anclaje

Además de describir el contenido de un patrón para que coincida, la ubicación relativa se puede especificar en el texto de entrada donde el patrón debe aparecer usando las instrucciones de anclado. the table below.

Códigos de anclado de expresiones regulares
Código Significado
^ Inicio de la cadena o línea
$ Fin de la cadena o línea
\A Inicio de la cadena
\Z Fin de la cadena
\b Cadena vacía al inicio o al final de una palabra
\B Cadena vacía no al inicio o al final de una palabra
re_anchoring.py
from re_test_patterns import test_patterns

test_patterns(
    'This is some text -- with punctuation.',
    [(r'^\w+', 'word at start of string'),
     (r'\A\w+', 'word at start of string'),
     (r'\w+\S*$', 'word near end of string'),
     (r'\w+\S*\Z', 'word near end of string'),
     (r'\w*t\w*', 'word containing t'),
     (r'\bt\w+', 't at start of word'),
     (r'\w+t\b', 't at end of word'),
     (r'\Bt\B', 't, not start or end of word')],
)

Los patrones en el ejemplo para encontrar palabras al principio y final de la cadena son diferentes porque la palabra al final de la cadena esta seguida por un punto para terminar la oración. Los patrones \w+$ no coincidiría, ya que . no se considera un caracter alfanumérico.

$ python3 re_anchoring.py

'^\w+' (word at start of string)

  'This is some text -- with punctuation.'
  'This'

'\A\w+' (word at start of string)

  'This is some text -- with punctuation.'
  'This'

'\w+\S*$' (word near end of string)

  'This is some text -- with punctuation.'
  ..........................'punctuation.'

'\w+\S*\Z' (word near end of string)

  'This is some text -- with punctuation.'
  ..........................'punctuation.'

'\w*t\w*' (word containing t)

  'This is some text -- with punctuation.'
  .............'text'
  .....................'with'
  ..........................'punctuation'

'\bt\w+' (t at start of word)

  'This is some text -- with punctuation.'
  .............'text'

'\w+t\b' (t at end of word)

  'This is some text -- with punctuation.'
  .............'text'

'\Bt\B' (t, not start or end of word)

  'This is some text -- with punctuation.'
  .......................'t'
  ..............................'t'
  .................................'t'

Restringir la búsqueda

En situaciones donde se sabe de antemano que solo un sub-conjunto de la entrada completa debe buscarse, la coincidencia de expresión regular puede ser más restringida al decirle a re el límite del rango de búsqueda. Por ejemplo, si el patrón debe aparecer al principio de la entrada, usar match() en lugar de search() anclará la búqueda sin tener que incluir explícitamente un ancla en el patrón de búsqueda.

re_match.py
import re

text = 'This is some text -- with punctuation.'
pattern = 'is'

print('Text   :', text)
print('Pattern:', pattern)

m = re.match(pattern, text)
print('Match  :', m)
s = re.search(pattern, text)
print('Search :', s)

Dado que el texto literal is no aparece al comienzo del texto de entrada, no se encuentra usando match(). La secuencia aparece otras dos veces en el texto, así que search() lo encuentra.

$ python3 re_match.py

Text   : This is some text -- with punctuation.
Pattern: is
Match  : None
Search : <_sre.SRE_Match object; span=(2, 4), match='is'>

El método fullmatch() requiere que toda la cadena de entrada coincida con el patrón.

re_fullmatch.py
import re

text = 'This is some text -- with punctuation.'
pattern = 'is'

print('Text       :', text)
print('Pattern    :', pattern)

m = re.search(pattern, text)
print('Search     :', m)
s = re.fullmatch(pattern, text)
print('Full match :', s)

Aquí search() muestra que el patrón aparece en la entrada, pero no consume toda la entrada por lo que fullmatch() no reporta una coincidencia.

$ python3 re_fullmatch.py

Text       : This is some text -- with punctuation.
Pattern    : is
Search     : <_sre.SRE_Match object; span=(2, 4), match='is'>
Full match : None

El método search() de una expresión regular compilada acepta parámetros de posición opcionales start y end para limitar la búsquedaa a una subcadena de la entrada.

re_search_substring.py
import re

text = 'This is some text -- with punctuation.'
pattern = re.compile(r'\b\w*is\w*\b')

print('Text:', text)
print()

pos = 0
while True:
    match = pattern.search(text, pos)
    if not match:
        break
    s = match.start()
    e = match.end()
    print('  {:>2d} : {:>2d} = "{}"'.format(
        s, e - 1, text[s:e]))
    # Move forward in text for the next search
    pos = e

Este ejemplo implementa una forma menos eficiente de iterall(). Cada vez que se encuentra una coincidencia, la posición final de esa coincidencia se utiliza para la próxima búsqueda.

$ python3 re_search_substring.py

Text: This is some text -- with punctuation.

   0 :  3 = "This"
   5 :  6 = "is"

Disección de coincidencias con grupos

La búsqueda de coincidencias de patrones es la base de las poderosas capacidades proporcionadas por expresiones regulares. Agregando grupos a un patrón aísla partes del texto que coincide, expandiéndo las capacidades para crear un analizador. Los grupos se definen al adjuntar patrones entre paréntesis.

re_groups.py
from re_test_patterns import test_patterns

test_patterns(
    'abbaaabbbbaaaaa',
    [('a(ab)', 'a followed by literal ab'),
     ('a(a*b*)', 'a followed by 0-n a and 0-n b'),
     ('a(ab)*', 'a followed by 0-n ab'),
     ('a(ab)+', 'a followed by 1-n ab')],
)

Cualquier expresión regular completa se puede convertir en un grupo y ser anidada dentro de una expresión más grande. Todos los modificadores de repetición pueden ser aplicados a un grupo como un todo, requiriendo que todo el patrón de grupo se repita.

$ python3 re_groups.py

'a(ab)' (a followed by literal ab)

  'abbaaabbbbaaaaa'
  ....'aab'

'a(a*b*)' (a followed by 0-n a and 0-n b)

  'abbaaabbbbaaaaa'
  'abb'
  ...'aaabbbb'
  ..........'aaaaa'

'a(ab)*' (a followed by 0-n ab)

  'abbaaabbbbaaaaa'
  'a'
  ...'a'
  ....'aab'
  ..........'a'
  ...........'a'
  ............'a'
  .............'a'
  ..............'a'

'a(ab)+' (a followed by 1-n ab)

  'abbaaabbbbaaaaa'
  ....'aab'

Para acceder a las subcadenas que coinciden con los grupos individuales dentro de un patrón, usa el método groups() del objeto Match.

re_groups_match.py
import re

text = 'This is some text -- with punctuation.'

print(text)
print()

patterns = [
    (r'^(\w+)', 'word at start of string'),
    (r'(\w+)\S*$', 'word at end, with optional punctuation'),
    (r'(\bt\w+)\W+(\w+)', 'word starting with t, another word'),
    (r'(\w+t)\b', 'word ending with t'),
]

for pattern, desc in patterns:
    regex = re.compile(pattern)
    match = regex.search(text)
    print("'{}' ({})\n".format(pattern, desc))
    print('  ', match.groups())
    print()

Match.groups() devuelve una secuencia de cadenas en el orden de los grupos dentro de la expresión que coinciden con la cadena.

$ python3 re_groups_match.py

This is some text -- with punctuation.

'^(\w+)' (word at start of string)

   ('This',)

'(\w+)\S*$' (word at end, with optional punctuation)

   ('punctuation',)

'(\bt\w+)\W+(\w+)' (word starting with t, another word)

   ('text', 'with')

'(\w+t)\b' (word ending with t)

   ('text',)

Para solicitar la coincidencia de un solo grupo, usa el método group(). Esto es útil cuando se usa la agrupación para encontrar partes de la cadena, pero algunas de las partes encontradas por grupos no son necesarias en los resultados.

re_groups_individual.py
import re

text = 'This is some text -- with punctuation.'

print('Input text            :', text)

# word starting with 't' then another word
regex = re.compile(r'(\bt\w+)\W+(\w+)')
print('Pattern               :', regex.pattern)

match = regex.search(text)
print('Entire match          :', match.group(0))
print('Word starting with "t":', match.group(1))
print('Word after "t" word   :', match.group(2))

El grupo 0 representa la cadena que coincide con la expresión completa, y los subgrupos se numeran comenzando con 1 en el orden en que el paréntesis izquierdo aparece en la expresión.

$ python3 re_groups_individual.py

Input text            : This is some text -- with punctuation.
Pattern               : (\bt\w+)\W+(\w+)
Entire match          : text -- with
Word starting with "t": text
Word after "t" word   : with

Python amplía la sintaxis básica de agrupamiento para agregar grupos nombrados. Utilizando nombres para referirse a grupos hace que sea más fácil modificar el patrón en el transcurso del tiempo, sin tener que modificar también el código usando los resultados de la coincidencia (match). Para establecer el nombre de un grupo, usa la sintaxis (?P<nombre>patrón).

re_groups_named.py
import re

text = 'This is some text -- with punctuation.'

print(text)
print()

patterns = [
    r'^(?P<first_word>\w+)',
    r'(?P<last_word>\w+)\S*$',
    r'(?P<t_word>\bt\w+)\W+(?P<other_word>\w+)',
    r'(?P<ends_with_t>\w+t)\b',
]

for pattern in patterns:
    regex = re.compile(pattern)
    match = regex.search(text)
    print("'{}'".format(pattern))
    print('  ', match.groups())
    print('  ', match.groupdict())
    print()

Usa groupdict() para recuperar los nombres de los grupos de mapas del diccionario a subcadenas de la coincidencia. Los patrones con nombre están incluidos también en la secuencia ordenada devuelta por groups().

$ python3 re_groups_named.py

This is some text -- with punctuation.

'^(?P<first_word>\w+)'
   ('This',)
   {'first_word': 'This'}

'(?P<last_word>\w+)\S*$'
   ('punctuation',)
   {'last_word': 'punctuation'}

'(?P<t_word>\bt\w+)\W+(?P<other_word>\w+)'
   ('text', 'with')
   {'t_word': 'text', 'other_word': 'with'}

'(?P<ends_with_t>\w+t)\b'
   ('text',)
   {'ends_with_t': 'text'}

Una versión actualizada de test_patterns() que muestra los grupos numerados y nombrados que coincidan con un patrón harán los siguientes ejemplos más fáciles de seguir.

re_test_patterns_groups.py
import re


def test_patterns(text, patterns):
    """Given source text and a list of patterns, look for
    matches for each pattern within the text and print
    them to stdout.
    """
    # Look for each pattern in the text and print the results
    for pattern, desc in patterns:
        print('{!r} ({})\n'.format(pattern, desc))
        print('  {!r}'.format(text))
        for match in re.finditer(pattern, text):
            s = match.start()
            e = match.end()
            prefix = ' ' * (s)
            print(
                '  {}{!r}{} '.format(prefix,
                                     text[s:e],
                                     ' ' * (len(text) - e)),
                end=' ',
            )
            print(match.groups())
            if match.groupdict():
                print('{}{}'.format(
                    ' ' * (len(text) - s),
                    match.groupdict()),
                )
        print()
    return

Como un grupo es en sí mismo una expresión regular completa, los grupos pueden ester anidados dentro de otros grupos para construir expresiones aún más complicadas.

re_groups_nested.py
from re_test_patterns_groups import test_patterns

test_patterns(
    'abbaabbba',
    [(r'a((a*)(b*))', 'a followed by 0-n a and 0-n b')],
)

En este caso, el grupo (a*) coincide con una cadena vacía, por lo que el valor de retorno de groups() incluye esa cadena vacía como valor que coincide.

$ python3 re_groups_nested.py

'a((a*)(b*))' (a followed by 0-n a and 0-n b)

  'abbaabbba'
  'abb'        ('bb', '', 'bb')
     'aabbb'   ('abbb', 'a', 'bbb')
          'a'  ('', '', '')

Los grupos también son útiles para especificar patrones alternativos. Utiliza el símbolo de tubería (|) para separar dos patrones e indicar que cualquiera de los patrones debería coincidir. Considera la colocación de la tubería con cuidado, sin embargo. La primera expresión en este ejemplo coincide con una secuencia de a seguida por una secuencia que consiste en una sola letra, a o b. El segundo patrón coincide con a seguido de una secuencia que puede incluir cualquiera a o b. Los patrones son similares, pero las coincidencias resultantes son completamente diferentes.

re_groups_alternative.py
from re_test_patterns_groups import test_patterns

test_patterns(
    'abbaabbba',
    [(r'a((a+)|(b+))', 'a then seq. of a or seq. of b'),
     (r'a((a|b)+)', 'a then seq. of [ab]')],
)

Cuando un grupo alternativo no corresponde, pero el patrón completo sí, el valor de retorno de groups() incluye un valor None en el punto de la secuencia donde debería aparecer el grupo alternativo.

$ python3 re_groups_alternative.py

'a((a+)|(b+))' (a then seq. of a or seq. of b)

  'abbaabbba'
  'abb'        ('bb', None, 'bb')
     'aa'      ('a', 'a', None)

'a((a|b)+)' (a then seq. of [ab])

  'abbaabbba'
  'abbaabbba'  ('bbaabbba', 'a')

Definir un grupo que contenga un subpatrón también es útil en casos donde la cadena que coincide con el subpatrón no es parte de lo que debería ser extraído del texto completo. Este tipo de grupos se llama sin captura. Los grupos sin captura se pueden usar para describir patrones de repetición o alternativas, sin aislar la porción de emparejamiento de la cadena en el valor devuelto. Para crear un grupo sin captura, usa la sintaxis (?:patrón).

re_groups_noncapturing.py
from re_test_patterns_groups import test_patterns

test_patterns(
    'abbaabbba',
    [(r'a((a+)|(b+))', 'capturing form'),
     (r'a((?:a+)|(?:b+))', 'noncapturing')],
)

En el siguiente ejemplo, compara los grupos devueltos para las formas de captura y sin captura de un patrón que coincide con los mismos resultados.

$ python3 re_groups_noncapturing.py

'a((a+)|(b+))' (capturing form)

  'abbaabbba'
  'abb'        ('bb', None, 'bb')
     'aa'      ('a', 'a', None)

'a((?:a+)|(?:b+))' (noncapturing)

  'abbaabbba'
  'abb'        ('bb',)
     'aa'      ('a',)

Opciones de búsqueda

Los indicadores de opción se utilizan para cambiar la forma en que el motor de coincidencia procesa una expresión. Las banderas se pueden combinar usando un OR a nivel de operaciones de bit, son pasadas luego a compile(), search(), match(), y otras funciones que aceptan un patrón para buscar.

Emparejamiento sin distinción de mayúsculas y minúsculas

IGNORECASE causa que caracteres literales y rangos de caracteres en el patrón para hacer coincidan tanto mayúsculas como minúsculas.

re_flags_ignorecase.py
import re

text = 'This is some text -- with punctuation.'
pattern = r'\bT\w+'
with_case = re.compile(pattern)
without_case = re.compile(pattern, re.IGNORECASE)

print('Text:\n  {!r}'.format(text))
print('Pattern:\n  {}'.format(pattern))
print('Case-sensitive:')
for match in with_case.findall(text):
    print('  {!r}'.format(match))
print('Case-insensitive:')
for match in without_case.findall(text):
    print('  {!r}'.format(match))

Dado que el patrón incluye el literal T, si IGNORECASE no es establecido, la única coincidencia es la palabra This. Cuando se ignora mayúsculas y minísculas, text también coincide.

$ python3 re_flags_ignorecase.py

Text:
  'This is some text -- with punctuation.'
Pattern:
  \bT\w+
Case-sensitive:
  'This'
Case-insensitive:
  'This'
  'text'

Entrada con múltiples líneas

Dos banderas afectan cómo funciona la búsqueda en la entrada de varias líneas: MULTILINE y DOTALL. La bandera MULTILINE controla cómo el código de coincidencia de patrones procesa instruccione de anclaje para el texto que contiene caracteres de nueva línea. Cuando el modo multilínea está activado, las reglas de anclaje para ^ y $ se aplican al principio y al final de cada línea, además de toda la cadena.

re_flags_multiline.py
import re

text = 'This is some text -- with punctuation.\nA second line.'
pattern = r'(^\w+)|(\w+\S*$)'
single_line = re.compile(pattern)
multiline = re.compile(pattern, re.MULTILINE)

print('Text:\n  {!r}'.format(text))
print('Pattern:\n  {}'.format(pattern))
print('Single Line :')
for match in single_line.findall(text):
    print('  {!r}'.format(match))
print('Multline    :')
for match in multiline.findall(text):
    print('  {!r}'.format(match))

El patrón en el ejemplo coincide con la primera o la última palabra de la entrada. Coincide con line. al final de la cadena, aunque no hay línea nueva.

$ python3 re_flags_multiline.py

Text:
  'This is some text -- with punctuation.\nA second line.'
Pattern:
  (^\w+)|(\w+\S*$)
Single Line :
  ('This', '')
  ('', 'line.')
Multline    :
  ('This', '')
  ('', 'punctuation.')
  ('A', '')
  ('', 'line.')

DOTALL es la otra bandera relacionada con el texto de líneas múltiples. Normalmente, el carácter de punto (.) coincide con todo en el texto de entrada, excepto un carácter de nueva línea. La bandera también permite que el punto coincida con las líneas nuevas.

re_flags_dotall.py
import re

text = 'This is some text -- with punctuation.\nA second line.'
pattern = r'.+'
no_newlines = re.compile(pattern)
dotall = re.compile(pattern, re.DOTALL)

print('Text:\n  {!r}'.format(text))
print('Pattern:\n  {}'.format(pattern))
print('No newlines :')
for match in no_newlines.findall(text):
    print('  {!r}'.format(match))
print('Dotall      :')
for match in dotall.findall(text):
    print('  {!r}'.format(match))

Sin la bandera, cada línea del texto de entrada coincide con el patrón por separado. Al agregar la bandera, se consume toda la cadena.

$ python3 re_flags_dotall.py

Text:
  'This is some text -- with punctuation.\nA second line.'
Pattern:
  .+
No newlines :
  'This is some text -- with punctuation.'
  'A second line.'
Dotall      :
  'This is some text -- with punctuation.\nA second line.'

Unicode

En Python 3, los objetos str usan el conjunto de carácteres Unicode completo, y el procesamiento de expresiones regulares en un str supone que el patrón y el texto de entrada son ambos Unicode. Los códigos de escape descritos anteriormente se definen en términos de Unicode por defecto. Esas suposiciones significan que el patrón \w+ coincidirá con las palabras «francés» y «Français». Para restringir los códigos de escape al conjunto de caracteres ASCII, como era el predeterminado en Python 2, usa el indicador ASCII al compilar el patrón o al llamar a las funciones de nivel de módulo search() y match().

re_flags_ascii.py
import re

text = u'Français złoty Österreich'
pattern = r'\w+'
ascii_pattern = re.compile(pattern, re.ASCII)
unicode_pattern = re.compile(pattern)

print('Text    :', text)
print('Pattern :', pattern)
print('ASCII   :', list(ascii_pattern.findall(text)))
print('Unicode :', list(unicode_pattern.findall(text)))

Las otras secuencias de escape (\W, \b, \B, \d, \D, \s, y \S también se procesan de manera diferente para el texto ASCII. En lugar de consultar la base de datos Unicode para encontrar las propiedades de cada caractér, re utiliza la definición ASCII del conjunto de caracteres.

$ python3 re_flags_ascii.py

Text    : Français złoty Österreich
Pattern : \w+
ASCII   : ['Fran', 'ais', 'z', 'oty', 'sterreich']
Unicode : ['Français', 'złoty', 'Österreich']

Sintaxis verbosa de expresión

El formato compacto de la sintaxis de expresión regular puede convertirse en un obstáculo a medida que las expresiones se vuelven más complicadas. A media que la cantidad de grupos en una expresión aumenta, será más trabajo hacer un seguimiento de por qué cada elemento es necesario y cómo exactamente interactúan las partes de la expresión. Usar grupos nombrados ayuda a mitigar estos problemas, pero una mejor solución es usar expresiones de modo verboso, que permiten incrustar comentarios y espacios en blanco adicionales en el patrón.

Un patrón para validar las direcciones de correo electrónico ilustrará cómo el modo verboso hace que trabajar con expresiones regulares sea más fácil. La primera versión reconoce las direcciones que finalizan en uno de los tres dominios de nivel superior: .com, .org, o .edu.

re_email_compact.py
import re

address = re.compile('[\w\d.+-]+@([\w\d.]+\.)+(com|org|edu)')

candidates = [
    u'first.last@example.com',
    u'first.last+category@gmail.com',
    u'valid-address@mail.example.com',
    u'not-valid@example.foo',
]

for candidate in candidates:
    match = address.search(candidate)
    print('{:<30}  {}'.format(
        candidate, 'Matches' if match else 'No match')
    )

Esta expresión ya es compleja. Hay varias clases de caractéres clases, grupos y expresiones de repetición

$ python3 re_email_compact.py

first.last@example.com          Matches
first.last+category@gmail.com   Matches
valid-address@mail.example.com  Matches
not-valid@example.foo           No match

Convertir la expresión a un formato más verbosos la hará más fácil de extender.

re_email_verbose.py
import re

address = re.compile(
    '''
    [\w\d.+-]+       # username
    @
    ([\w\d.]+\.)+    # domain name prefix
    (com|org|edu)    # TODO: support more top-level domains
    ''',
    re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'first.last+category@gmail.com',
    u'valid-address@mail.example.com',
    u'not-valid@example.foo',
]

for candidate in candidates:
    match = address.search(candidate)
    print('{:<30}  {}'.format(
        candidate, 'Matches' if match else 'No match'),
    )

La expresión coincide con las mismas entradas, pero en este formato extendido es más fácil de leer. Los comentarios también ayudan a identificar diferentes partes del patrón para que pueda expandirse y que coincida con más entradas.

$ python3 re_email_verbose.py

first.last@example.com          Matches
first.last+category@gmail.com   Matches
valid-address@mail.example.com  Matches
not-valid@example.foo           No match

Esta versión expandida analiza entradas que incluyen el nombre de una persona y una dirección de correo electrónico, como podría aparecer en un encabezado de correo electrónico. El nombre viene primero y se sostiene por sí mismo, y la dirección de correo electrónico sigue, rodeada por corchetes angulares (< y >).

re_email_with_name.py
import re

address = re.compile(
    '''

    # A name is made up of letters, and may include "."
    # for title abbreviations and middle initials.
    ((?P<name>
       ([\w.,]+\s+)*[\w.,]+)
       \s*
       # Email addresses are wrapped in angle
       # brackets < >, but only if a name is
       # found, so keep the start bracket in this
       # group.
       <
    )? # the entire name is optional

    # The address itself: username@domain.tld
    (?P<email>
      [\w\d.+-]+       # username
      @
      ([\w\d.]+\.)+    # domain name prefix
      (com|org|edu)    # limit the allowed top-level domains
    )

    >? # optional closing angle bracket
    ''',
    re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'first.last+category@gmail.com',
    u'valid-address@mail.example.com',
    u'not-valid@example.foo',
    u'First Last <first.last@example.com>',
    u'No Brackets first.last@example.com',
    u'First Last',
    u'First Middle Last <first.last@example.com>',
    u'First M. Last <first.last@example.com>',
    u'<first.last@example.com>',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Name :', match.groupdict()['name'])
        print('  Email:', match.groupdict()['email'])
    else:
        print('  No match')

Al igual que con otros lenguajes de programación, la capacidad de insertar comentarios en expresiones regulares verbosas ayuda con su mantenibilidad. Esta versión final incluye notas de implementación para futuros mantenedores y espacios en blanco para separar los grupos entre sí y resaltar su nivel de anidación

$ python3 re_email_with_name.py

Candidate: first.last@example.com
  Name : None
  Email: first.last@example.com
Candidate: first.last+category@gmail.com
  Name : None
  Email: first.last+category@gmail.com
Candidate: valid-address@mail.example.com
  Name : None
  Email: valid-address@mail.example.com
Candidate: not-valid@example.foo
  No match
Candidate: First Last <first.last@example.com>
  Name : First Last
  Email: first.last@example.com
Candidate: No Brackets first.last@example.com
  Name : None
  Email: first.last@example.com
Candidate: First Last
  No match
Candidate: First Middle Last <first.last@example.com>
  Name : First Middle Last
  Email: first.last@example.com
Candidate: First M. Last <first.last@example.com>
  Name : First M. Last
  Email: first.last@example.com
Candidate: <first.last@example.com>
  Name : None
  Email: first.last@example.com

Banderas incrustadas en patrones

En situaciones donde los indicadores no se pueden agregar al compilar una expresión, como cuando un patrón se pasa como un argumento a una función de biblioteca que lo compilará más tarde, los indicadores pueden ser integrados dentro de la cadena de expresión en sí. Por ejemplo, para activar la coincidencia insensible a mayúsculas y minúsculas, agrega (?i) al comienzo de la expresión.

re_flags_embedded.py
import re

text = 'This is some text -- with punctuation.'
pattern = r'(?i)\bT\w+'
regex = re.compile(pattern)

print('Text      :', text)
print('Pattern   :', pattern)
print('Matches   :', regex.findall(text))

Porque las opciones controlan la forma en que se evalúa o analiza toda la expresión, siempre deben aparecer al comienzo de la expresión.

$ python3 re_flags_embedded.py

Text      : This is some text -- with punctuation.
Pattern   : (?i)\bT\w+
Matches   : ['This', 'text']

Las abreviaturas para todos los indicadores se enumeran en the table below.

Abreviaturas de banderas de expreciones regulares
Bandera Abreviatura
ASCII a
IGNORECASE i
MULTILINE m
DOTALL s
VERBOSE x

Las banderas embebidas pueden combinarse colocándolas dentro del mismo grupo. Por ejemplo, (?im) activa la coincidencia insensible a mayúsculas / minúsculas para cadenas multilínea.

Mirando hacia adelante o atrás

En muchos casos, es útil hacer coincidir una parte de un patrón solo si alguna otra parte también coincidirá. Por ejemplo, en la expresión de análisis sintáctico de correo electrónico, los corchetes angulares se marcaron como opcionales. De modo realista, los paréntesis deberían estar emparejados, y la expresión debe coincidir solo si ambos están presentes, o ninguno lo es. Esta versión modificada de la expresión utiliza una afirmación de mirar hacia adelante para hacer coincidir el par. La sintaxis de afirmación de mirar hacia delante es (?=patrón).

re_look_ahead.py
import re

address = re.compile(
    '''
    # A name is made up of letters, and may include "."
    # for title abbreviations and middle initials.
    ((?P<name>
       ([\w.,]+\s+)*[\w.,]+
     )
     \s+
    ) # name is no longer optional

    # LOOKAHEAD
    # Email addresses are wrapped in angle brackets, but only
    # if both are present or neither is.
    (?= (<.*>$)       # remainder wrapped in angle brackets
        |
        ([^<].*[^>]$) # remainder *not* wrapped in angle brackets
      )

    <? # optional opening angle bracket

    # The address itself: username@domain.tld
    (?P<email>
      [\w\d.+-]+       # username
      @
      ([\w\d.]+\.)+    # domain name prefix
      (com|org|edu)    # limit the allowed top-level domains
    )

    >? # optional closing angle bracket
    ''',
    re.VERBOSE)

candidates = [
    u'First Last <first.last@example.com>',
    u'No Brackets first.last@example.com',
    u'Open Bracket <first.last@example.com',
    u'Close Bracket first.last@example.com>',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Name :', match.groupdict()['name'])
        print('  Email:', match.groupdict()['email'])
    else:
        print('  No match')

Hay varios cambios importantes en esta versión de la expresión. Primero, la porción del nombre ya no es opcional. Eso significa que las direcciones autónomas no coinciden, pero también impide que combinaciones incorrectamente formateadas de nombre/dirección coincidan. La regla de mirar hacia adelante positivamente después de que el grupo «name» afirme que el resto de la cadena está envuelta con un par de corchetes angulares, o no hay un desajustes de corchetes; cualquiera de los dos o ninguno de los corchetes está presente. La mirada hacia adelante se expresa como un grupo, pero la coincidencia para un grupo de mirada hacia adelante no consume ningún texto de entrada, por lo que el resto del patrón se reanuda desde el mismo lugar después de que la mirada hacia adelante coincide.

$ python3 re_look_ahead.py

Candidate: First Last <first.last@example.com>
  Name : First Last
  Email: first.last@example.com
Candidate: No Brackets first.last@example.com
  Name : No Brackets
  Email: first.last@example.com
Candidate: Open Bracket <first.last@example.com
  No match
Candidate: Close Bracket first.last@example.com>
  No match

Una afirmación de búsqueda hacia adelante de aspecto negativo ((?!patrón)) dice que el patrón no coincide con el texto que sigue al punto actual. Por ejemplo, el patrón de reconocimiento de correo electrónico podría modificarse para ignorar las direcciones de correo noreply comúnmente usadas por los sistemas automatizados.

re_negative_look_ahead.py
import re

address = re.compile(
    '''
    ^

    # An address: username@domain.tld

    # Ignore noreply addresses
    (?!noreply@.*$)

    [\w\d.+-]+       # username
    @
    ([\w\d.]+\.)+    # domain name prefix
    (com|org|edu)    # limit the allowed top-level domains

    $
    ''',
    re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'noreply@example.com',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Match:', candidate[match.start():match.end()])
    else:
        print('  No match')

La dirección que comienza con noreply no coincide con el patrón, ya que la afirmación de mirar hacia adelante falla.

$ python3 re_negative_look_ahead.py

Candidate: first.last@example.com
  Match: first.last@example.com
Candidate: noreply@example.com
  No match

En lugar de mirar hacia adelante en busca de noreply en la porción de nombre de usuario de la dirección de correo electrónico, el patrón puede escribirse alternativamente con un afirmación de hacia atras de aspecto negativo después de que el nombre de usuario coincida usando la sintaxis (?<!patrón).

re_negative_look_behind.py
import re

address = re.compile(
    '''
    ^

    # An address: username@domain.tld

    [\w\d.+-]+       # username

    # Ignore noreply addresses
    (?<!noreply)

    @
    ([\w\d.]+\.)+    # domain name prefix
    (com|org|edu)    # limit the allowed top-level domains

    $
    ''',
    re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'noreply@example.com',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Match:', candidate[match.start():match.end()])
    else:
        print('  No match')

Mirar hacia atrás funciona de manera un poco diferente que mirar hacia adelante, en que la expresión debe usar un patrón de longitud fija. Las repeticiones son permitidas, siempre que haya un número fijo de ellas (sin comodines o rangos).

$ python3 re_negative_look_behind.py

Candidate: first.last@example.com
  Match: first.last@example.com
Candidate: noreply@example.com
  No match

Una aserción hacia atrás de apariencia positiva se puede utilizar para encontrar texto que sigue a un patrón usando la sintaxis (?<=patrón). En el siguiente ejemplo, la expresión encuentra los usuarios de Twitter.

re_look_behind.py
import re

twitter = re.compile(
    '''
    # A twitter handle: @username
    (?<=@)
    ([\w\d_]+)       # username
    ''',
    re.VERBOSE)

text = '''This text includes two Twitter handles.
One for @ThePSF, and one for the author, @doughellmann.
'''

print(text)
for match in twitter.findall(text):
    print('Handle:', match)

El patrón coincide con secuencias de caracteres que pueden formar un usuario de Twitter, siempre que estén precedidos por un @.

$ python3 re_look_behind.py

This text includes two Twitter handles.
One for @ThePSF, and one for the author, @doughellmann.

Handle: ThePSF
Handle: doughellmann

Expresiones autorreferenciales

Los valores coincidentes se pueden usar en partes posteriores de una expresión. Por ejemplo, el ejemplo del correo electrónico se puede actualizar para que coincida solo con las direcciones compuestas por los nombres y apellidos de la persona al incluir referencias retrospectivas a esos grupos. La forma más fácil de lograr esto es haciendo referencia al grupo previamente coincidente por número de identificación, usando \num.

re_refer_to_group.py
import re

address = re.compile(
    r'''

    # The regular name
    (\w+)               # first name
    \s+
    (([\w.]+)\s+)?      # optional middle name or initial
    (\w+)               # last name

    \s+

    <

    # The address: first_name.last_name@domain.tld
    (?P<email>
      \1               # first name
      \.
      \4               # last name
      @
      ([\w\d.]+\.)+    # domain name prefix
      (com|org|edu)    # limit the allowed top-level domains
    )

    >
    ''',
    re.VERBOSE | re.IGNORECASE)

candidates = [
    u'First Last <first.last@example.com>',
    u'Different Name <first.last@example.com>',
    u'First Middle Last <first.last@example.com>',
    u'First M. Last <first.last@example.com>',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Match name :', match.group(1), match.group(4))
        print('  Match email:', match.group(5))
    else:
        print('  No match')

Aunque la sintaxis es simple, crear referencias retrospectivas por ID numérico tiene algunas desventajas. Desde un punto de vista práctico, a medida que la expresión cambia, los grupos deben contar de nuevo y cada referencia puede necesitar ser actualizada. Otra desventaja es que solo se pueden hacer 99 referencias usando la sintaxis de referencia estándar \n, porque si el número de ID tiene tres dígitos, será interpretado como un valor de carácter octal en lugar de una referencia de grupo. Por supuesto, si hay más de 99 grupos en una expresión, habrá desafíos de mantenimiento más serios que simplemente no ser capaz de referirse a todos ellos.

$ python3 re_refer_to_group.py

Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com
Candidate: Different Name <first.last@example.com>
  No match
Candidate: First Middle Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com
Candidate: First M. Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

El analizador de expresiones de Python incluye una extensión que usa (?P=nombre) para referirse al valor de un grupo nombrado que coincide con anterioridad en la expresión.

re_refer_to_named_group.py
import re

address = re.compile(
    '''

    # The regular name
    (?P<first_name>\w+)
    \s+
    (([\w.]+)\s+)?      # optional middle name or initial
    (?P<last_name>\w+)

    \s+

    <

    # The address: first_name.last_name@domain.tld
    (?P<email>
      (?P=first_name)
      \.
      (?P=last_name)
      @
      ([\w\d.]+\.)+    # domain name prefix
      (com|org|edu)    # limit the allowed top-level domains
    )

    >
    ''',
    re.VERBOSE | re.IGNORECASE)

candidates = [
    u'First Last <first.last@example.com>',
    u'Different Name <first.last@example.com>',
    u'First Middle Last <first.last@example.com>',
    u'First M. Last <first.last@example.com>',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Match name :', match.groupdict()['first_name'],
              end=' ')
        print(match.groupdict()['last_name'])
        print('  Match email:', match.groupdict()['email'])
    else:
        print('  No match')

La expresión de dirección se compila con la bandera IGNORECASE activada, ya que los nombres propios normalmente están en mayúscula pero las direcciones de correo electrónico no lo están.

$ python3 re_refer_to_named_group.py

Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com
Candidate: Different Name <first.last@example.com>
  No match
Candidate: First Middle Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com
Candidate: First M. Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

El otro mecanismo para usar referencias retrospectivas en expresiones elige un patrón diferente basado en si un grupo anterior coincidió. El patrón de correo electrónico puede corregirse para que los corchetes angulares sean requeridos si hay un nombre presente, y no es obligatorio si la dirección de correo electrónico está sola. La sintaxis para probar si un grupo ha coincidido es (?(id)expresión-si|expresión-no), donde id es el nombre o número de grupo, expresión-si es el patrón a usar si el grupo tiene un valor, y expresión-no es el patrón para usar en caso contrario.

re_id.py
import re

address = re.compile(
    '''
    ^

    # A name is made up of letters, and may include "."
    # for title abbreviations and middle initials.
    (?P<name>
       ([\w.]+\s+)*[\w.]+
     )?
    \s*

    # Email addresses are wrapped in angle brackets, but
    # only if a name is found.
    (?(name)
      # remainder wrapped in angle brackets because
      # there is a name
      (?P<brackets>(?=(<.*>$)))
      |
      # remainder does not include angle brackets without name
      (?=([^<].*[^>]$))
     )

    # Look for a bracket only if the look-ahead assertion
    # found both of them.
    (?(brackets)<|\s*)

    # The address itself: username@domain.tld
    (?P<email>
      [\w\d.+-]+       # username
      @
      ([\w\d.]+\.)+    # domain name prefix
      (com|org|edu)    # limit the allowed top-level domains
     )

    # Look for a bracket only if the look-ahead assertion
    # found both of them.
    (?(brackets)>|\s*)

    $
    ''',
    re.VERBOSE)

candidates = [
    u'First Last <first.last@example.com>',
    u'No Brackets first.last@example.com',
    u'Open Bracket <first.last@example.com',
    u'Close Bracket first.last@example.com>',
    u'no.brackets@example.com',
]

for candidate in candidates:
    print('Candidate:', candidate)
    match = address.search(candidate)
    if match:
        print('  Match name :', match.groupdict()['name'])
        print('  Match email:', match.groupdict()['email'])
    else:
        print('  No match')

Esta versión del analizador de direcciones de correo electrónico usa dos pruebas. Si el grupo name coincide, entonces la afirmación de mirar hacia adelante requiere ambos corchetes angulares y configura el grupo brackets. Si name no es emparejado, la afirmación requiere que el resto del texto no tenga corchetes angulares a su alrededor. Más tarde, si se establece el grupo brackets, el código de coincidencia de patrón real consume los corchetes en la entrada usando patrones literales; de lo contrario, consume cualquier espacio en blanco.

$ python3 re_id.py

Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com
Candidate: No Brackets first.last@example.com
  No match
Candidate: Open Bracket <first.last@example.com
  No match
Candidate: Close Bracket first.last@example.com>
  No match
Candidate: no.brackets@example.com
  Match name : None
  Match email: no.brackets@example.com

Modificando cadenas con patrones

Además de buscar a través del texto, re permite modificar texto usando expresiones regulares como el mecanismo de búsqueda, y los reemplazos pueden hacer referencia a los grupos que coinciden en el patrón como parte del texto de sustitución. Usa sub() para reemplazar todas las ocurrencias de un patrón con otra cadena.

re_sub.py
import re

bold = re.compile(r'\*{2}(.*?)\*{2}')

text = 'Make this **bold**.  This **too**.'

print('Text:', text)
print('Bold:', bold.sub(r'<b>\1</b>', text))

Las referencias al texto que coincide con el patrón se pueden insertar usando la sintaxis \num usada para referencias anteriores.

$ python3 re_sub.py

Text: Make this **bold**.  This **too**.
Bold: Make this <b>bold</b>.  This <b>too</b>.

Para usar grupos con nombre en la sustitución, usa la sintaxis \g<nombre>.

re_sub_named_groups.py
import re

bold = re.compile(r'\*{2}(?P<bold_text>.*?)\*{2}')

text = 'Make this **bold**.  This **too**.'

print('Text:', text)
print('Bold:', bold.sub(r'<b>\g<bold_text></b>', text))

La sintaxis \g<nombre> también funciona con referencias numeradas y elimina cualquier ambigüedad entre los números de grupo y los dígitos literales al rededor.

$ python3 re_sub_named_groups.py

Text: Make this **bold**.  This **too**.
Bold: Make this <b>bold</b>.  This <b>too</b>.

Pasa un valor a count para limitar el número de sustituciones realizadas.

re_sub_count.py
import re

bold = re.compile(r'\*{2}(.*?)\*{2}')

text = 'Make this **bold**.  This **too**.'

print('Text:', text)
print('Bold:', bold.sub(r'<b>\1</b>', text, count=1))

Solo se realiza la primera sustitución porque count es 1.

$ python3 re_sub_count.py

Text: Make this **bold**.  This **too**.
Bold: Make this <b>bold</b>.  This **too**.

subn() funciona igual que sub() excepto que devuelve ambos, la cadena modificada y el recuento de sustituciones realizadas.

re_subn.py
import re

bold = re.compile(r'\*{2}(.*?)\*{2}')

text = 'Make this **bold**.  This **too**.'

print('Text:', text)
print('Bold:', bold.subn(r'<b>\1</b>', text))

El patrón de búsqueda coincide dos veces en el ejemplo.

$ python3 re_subn.py

Text: Make this **bold**.  This **too**.
Bold: ('Make this <b>bold</b>.  This <b>too</b>.', 2)

Dividiendo con patrones

str.split() es uno de los métodos más utilizados para romper cadenas para analizarlas. Solo admite el uso de valores literales como separadores, sin embargo, a veces una expresión regular es necesaria si la entrada no está formateada de forma consistente. Por ejemplo, muchos lenguajes de marcado de texto sin formato definen separadores de párrafo como dos o más caracteres de nueva línea (\n). En este caso, str.split() no se puede usar debido a la parte «o más» de la definición.

Una estrategia para identificar párrafos usando findall() usaría un patrón como (.+?)\n{2,}.

re_paragraphs_findall.py
import re

text = '''Paragraph one
on two lines.

Paragraph two.


Paragraph three.'''

for num, para in enumerate(re.findall(r'(.+?)\n{2,}',
                                      text,
                                      flags=re.DOTALL)
                           ):
    print(num, repr(para))
    print()

Ese patrón falla para los párrafos al final del texto de entrada, como es ilustrado por el hecho de que «Paragraph three.» no es parte de la salida.

$ python3 re_paragraphs_findall.py

0 'Paragraph one\non two lines.'

1 'Paragraph two.'

Extender el patrón para decir que un párrafo termina con dos o más líneas nuevas o el final de la entrada corrige el problema, pero hace más complicado el patrón. Convertir a re.split() en lugar de re.findall() maneja la condición de límite automáticamente y mantiene el patrón más simple.

re_split.py
import re

text = '''Paragraph one
on two lines.

Paragraph two.


Paragraph three.'''

print('With findall:')
for num, para in enumerate(re.findall(r'(.+?)(\n{2,}|$)',
                                      text,
                                      flags=re.DOTALL)):
    print(num, repr(para))
    print()

print()
print('With split:')
for num, para in enumerate(re.split(r'\n{2,}', text)):
    print(num, repr(para))
    print()

El argumento de patrón para split() expresa la especificacion de marcado más precisamente. Dos o más caracteres de línea nueva marcan un punto de separación entre los párrafos en la cadena de entrada.

$ python3 re_split.py

With findall:
0 ('Paragraph one\non two lines.', '\n\n')

1 ('Paragraph two.', '\n\n\n')

2 ('Paragraph three.', '')

With split:
0 'Paragraph one\non two lines.'

1 'Paragraph two.'

2 'Paragraph three.'

Encerrar la expresión entre paréntesis para definir un grupo causa que split() funcione más como str.partition(), por lo que devuelve los valores del separador, así como las otras partes de la cadena.

re_split_groups.py
import re

text = '''Paragraph one
on two lines.

Paragraph two.


Paragraph three.'''

print('With split:')
for num, para in enumerate(re.split(r'(\n{2,})', text)):
    print(num, repr(para))
    print()

La salida ahora incluye cada párrafo, así como la secuencia de líneas nuevas que los separan.

$ python3 re_split_groups.py

With split:
0 'Paragraph one\non two lines.'

1 '\n\n'

2 'Paragraph two.'

3 '\n\n\n'

4 'Paragraph three.'

Ver también

  • Standard library documentation for re
  • HOWTO de Expresión regular – Introducción de Andrew Kuchling a expresiones regulares para desarrolladores Python.
  • Kodos – Una herramienta interactiva de prueba de expresión regular de Phil Schwartz.
  • pythex – Una herramienta Web para probar expresiones regulares creada por Gabriel Rodríguez. Inspirada por Rubular.
  • Wikipedia: Regular expression – Introducción general a conceptos y técnicas de expresiónes regulares.
  • locale – Utiliza el módulo locale para establecer el idioma para trabajar con texto Unicode.
  • unicodedata – Acceso programático a la base de datos de propiedades de caractérs Unicode.