2.36. Grupos y anclas#

Un patrón puede hacer algo más que decir «esta cadena coincide»: puede separar las partes coincidentes y entregar cada una a la aplicación por su nombre. Los paréntesis alrededor de una parte de un patrón la convierten en un grupo de captura; el objeto de coincidencia expone entonces cada grupo como una subcadena independiente.

2.36.1. Grupos de captura#

Envuelve cualquier parte de un patrón en (...) para capturar lo que coincidió:

>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
  • El grupo 0 es siempre la coincidencia completa.

  • Los grupos 1, 2, … son las subcadenas capturadas, numeradas de izquierda a derecha por su paréntesis de apertura.

  • Llamar a match.group() con un índice posterior al último grupo provoca un IndexError.

Un patrón habitual es «coincidir con una estructura conocida y capturar las partes variables como enteros»:

def parse_temp(line):
    m = re.search(r'temp (\d+) at (\d+)s', line)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2))

2.36.2. Grupos sin captura#

Los paréntesis también agrupan una subexpresión para que un cuantificador pueda aplicarse a todo el grupo. Ese es el único propósito de la agrupación en r'(ab)+': «uno o más de ab». El hecho de que ab aparezca como grupo 1 es un efecto secundario.

Para agrupar sin capturar, usa (?:...):

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

Los grupos sin captura mantienen los números de grupo ordenados cuando un patrón usa la agrupación para dar estructura pero no le interesa extraer cada parte.

2.36.3. Anclas#

Las anclas no coinciden con un carácter: coinciden con una posición.

  • ^ – inicio de la cadena.

  • $ – final de la cadena.

Las anclas son lo que hace que re.match() y re.search() se comporten de forma diferente. re.match(p, s) es lo mismo que re.search('^' + p, s): obliga al patrón a empezar en la posición 0. Añadir $ al final de un patrón hace entonces que el patrón coincida con la cadena entera y nada más:

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

^ y $ en el módulo re de MicroPython siempre significan el inicio y el final de la cadena completa pasada a re.search(). No existe ninguna bandera re.MULTILINE que haga que coincidan en cada salto de línea incrustado, y $ tampoco coincide con la posición anterior a un \n final: tiene que ser el final absoluto de la entrada. Para obtener un comportamiento por línea, divide primero la entrada en saltos de línea y ejecuta el patrón en cada línea.

2.36.4. Conjuntos de caracteres#

Los corchetes definen un conjunto explícito de caracteres. La coincidencia consume exactamente un carácter del conjunto.

  • [abc] – uno de a, b, c.

  • [a-z] – un carácter en el rango a-z (ambos incluidos).

  • [a-zA-Z0-9] – letras o dígitos. Tres rangos combinados.

  • [^abc]no uno de a, b, c. El ^ solo niega cuando es el primer carácter dentro de los corchetes.

Ejemplos:

>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True

La primera llamada devuelve None en la práctica porque el texto literal está en minúsculas. El módulo re de MicroPython no tiene ninguna bandera re.IGNORECASE: para coincidir sin distinguir mayúsculas y minúsculas, escribe ambos casos dentro del conjunto:

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

Los atajos de clase (\d, \s, \w y sus formas negadas) también pueden usarse dentro de [...]: [\w-] significa «caracteres de palabra o un guion literal».

2.36.5. Cuantificadores codiciosos frente a perezosos#

Los cuantificadores *, +, ? y {m,n} son codiciosos de forma predeterminada: coinciden con tantos caracteres como el resto del patrón siga permitiendo. A menudo eso es exactamente lo que se quiere; a veces no:

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

El .+ codicioso capturó hasta el último >. Añadir ? hace que el cuantificador sea perezoso: coincide con lo menos posible:

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

La forma perezosa se detiene en el primer >. Los cuantificadores perezosos aparecen constantemente al extraer delimitadores equilibrados de una cadena.

2.36.6. Retrorreferencias en la sustitución#

re.sub() puede referirse a los grupos capturados en la cadena de reemplazo mediante \1, \2, … La sustitución reescribe cada coincidencia usando las partes capturadas:

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

Cada coincidencia captura dos números, y el reemplazo los intercambia. \g<1> es una sintaxis alternativa para lo mismo: útil cuando el siguiente carácter en el reemplazo es un dígito (r'\g<1>0' para añadir un cero literal al grupo 1 en lugar de leer «grupo 10»).

2.36.7. Lo que no está disponible#

Un recordatorio de lo que el módulo re de MicroPython no admite, por si un patrón de CPython acaba aquí y te sorprende:

  • Anticipación (?=...) y retrospección (?<=...) – no implementadas.

  • Grupos con nombre (?P<name>...) y retrorreferencias con nombre (?P=name) – no implementados.

  • Constantes de banderas como re.IGNORECASE, re.MULTILINE, re.DOTALL – no se respetan. Construye tú mismo el conjunto sin distinción de mayúsculas y minúsculas o divide previamente la entrada.

  • Los métodos match.groups(), match.span(), match.start() y match.end() están condicionados a un nivel de ROM que ninguna placa OpenMV de las distribuidas habilita. El código que dependa de ellos no se ejecutará en la cámara.

Con patrones, grupos y anclas, el conjunto de herramientas de expresiones regulares de la cámara es lo bastante pequeño para aprenderlo de una sentada y lo bastante completo para hacer todo, salvo el análisis sensible al contexto.